Skip to content

Commit

Permalink
[processor/redaction] introduce allowed_values parameter in process…
Browse files Browse the repository at this point in the history
…or config (open-telemetry#37638)

<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description

Introduce `allowed_values` parameter to the processor config.

If the value of an allowed key matches the regular expression for an
allowed value, the matching
part of the value is not masked even if it matches the regular
expression for a blocked value.

<!-- Issue number (e.g. open-telemetry#1234) or full URL to issue, if applicable. -->
#### Link to tracking issue
Fixes open-telemetry#35840

---------

Signed-off-by: odubajDT <[email protected]>
  • Loading branch information
odubajDT authored and khushijain21 committed Feb 14, 2025
1 parent c73e663 commit 1522978
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 83 deletions.
27 changes: 27 additions & 0 deletions .chloggen/redaction-allowed-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: processor/redaction

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: "Introduce 'allowed_values' parameter for allowed values of attributes"

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [35840]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
12 changes: 9 additions & 3 deletions processor/redactionprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ processors:
blocked_values:
- "4[0-9]{12}(?:[0-9]{3})?" ## Visa credit card number
- "(5[1-5][0-9]{14})" ## MasterCard number
# AllowedValues is a list of regular expressions for allowing values of
# blocked span attributes. Values that match are not masked.
allowed_values:
- "[email protected]"
# summary controls the verbosity level of the diagnostic attributes that
# the processor adds to the spans/logs/datapoints when it redacts or masks other
# attributes. In some contexts a list of redacted attributes leaks
Expand All @@ -101,9 +105,11 @@ If `allowed_keys` is empty, then no attributes are allowed. All
attributes are removed in that case. To keep all span attributes, you should
explicitly set `allow_all_keys` to true.

`blocked_values` applies to the values of the allowed keys. If the value of an
allowed key matches the regular expression for a blocked value, the matching
part of the value is then masked with a fixed length of asterisks.
`blocked_values` and `allowed_values` applies to the values of the allowed keys.
If the value of an allowed key matches the regular expression for an allowed value, the matching
part of the value is not masked even if it matches the regular expression for a blocked value.
If the value matches the regular expression for a blocked value only, the matching
part of the value is masked with a fixed length of asterisks.

For example, if `notes` is on the list of allowed keys, then the `notes`
attribute is retained. However, if there is a value such as a credit card
Expand Down
6 changes: 5 additions & 1 deletion processor/redactionprocessor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ type Config struct {
IgnoredKeys []string `mapstructure:"ignored_keys"`

// BlockedValues is a list of regular expressions for blocking values of
// allowed span attributes. Values that match are masked
// allowed span attributes. Values that match are masked.
BlockedValues []string `mapstructure:"blocked_values"`

// AllowedValues is a list of regular expressions for allowing values of
// blocked span attributes. Values that match are not masked.
AllowedValues []string `mapstructure:"allowed_values"`

// Summary controls the verbosity level of the diagnostic attributes that
// the processor adds to the spans when it redacts or masks other
// attributes. In some contexts a list of redacted attributes leaks
Expand Down
1 change: 1 addition & 0 deletions processor/redactionprocessor/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func TestLoadConfig(t *testing.T) {
AllowedKeys: []string{"description", "group", "id", "name"},
IgnoredKeys: []string{"safe_attribute"},
BlockedValues: []string{"4[0-9]{12}(?:[0-9]{3})?", "(5[1-5][0-9]{14})"},
AllowedValues: []string{"[email protected]"},
Summary: debug,
},
},
Expand Down
1 change: 1 addition & 0 deletions processor/redactionprocessor/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func TestDefaultConfiguration(t *testing.T) {
c := createDefaultConfig().(*Config)
assert.Empty(t, c.AllowedKeys)
assert.Empty(t, c.BlockedValues)
assert.Empty(t, c.AllowedValues)
}

func TestCreateTestProcessor(t *testing.T) {
Expand Down
54 changes: 38 additions & 16 deletions processor/redactionprocessor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type redaction struct {
ignoreList map[string]string
// Attribute values blocked in a span
blockRegexList map[string]*regexp.Regexp
// Attribute values allowed in a span
allowRegexList map[string]*regexp.Regexp
// Redaction processor configuration
config *Config
// Logger
Expand All @@ -36,16 +38,23 @@ type redaction struct {
func newRedaction(ctx context.Context, config *Config, logger *zap.Logger) (*redaction, error) {
allowList := makeAllowList(config)
ignoreList := makeIgnoreList(config)
blockRegexList, err := makeBlockRegexList(ctx, config)
blockRegexList, err := makeRegexList(ctx, config.BlockedValues)
if err != nil {
// TODO: Placeholder for an error metric in the next PR
return nil, fmt.Errorf("failed to process block list: %w", err)
}

allowRegexList, err := makeRegexList(ctx, config.AllowedValues)
if err != nil {
// TODO: Placeholder for an error metric in the next PR
return nil, fmt.Errorf("failed to process allow list: %w", err)
}

return &redaction{
allowList: allowList,
ignoreList: ignoreList,
blockRegexList: blockRegexList,
allowRegexList: allowRegexList,
config: config,
logger: logger,
}, nil
Expand Down Expand Up @@ -159,6 +168,7 @@ func (s *redaction) processAttrs(_ context.Context, attributes pcommon.Map) {
// TODO: Use the context for recording metrics
var toDelete []string
var toBlock []string
var allowed []string
var ignoring []string

// Identify attributes to redact and mask in the following sequence
Expand Down Expand Up @@ -186,8 +196,17 @@ func (s *redaction) processAttrs(_ context.Context, attributes pcommon.Map) {
}
}

// Mask any blocked values for the other attributes
strVal := value.Str()

// Allow any values matching the allowed list regex
for _, compiledRE := range s.allowRegexList {
if match := compiledRE.MatchString(strVal); match {
allowed = append(allowed, k)
return true
}
}

// Mask any blocked values for the other attributes
var matched bool
for _, compiledRE := range s.blockRegexList {
match := compiledRE.MatchString(strVal)
Expand All @@ -212,6 +231,7 @@ func (s *redaction) processAttrs(_ context.Context, attributes pcommon.Map) {
// Add diagnostic information to the span
s.addMetaAttrs(toDelete, attributes, redactedKeys, redactedKeyCount)
s.addMetaAttrs(toBlock, attributes, maskedValues, maskedValueCount)
s.addMetaAttrs(allowed, attributes, allowedValues, allowedValueCount)
s.addMetaAttrs(ignoring, attributes, "", ignoredKeyCount)
}

Expand Down Expand Up @@ -239,13 +259,15 @@ func (s *redaction) addMetaAttrs(redactedAttrs []string, attributes pcommon.Map,
}

const (
debug = "debug"
info = "info"
redactedKeys = "redaction.redacted.keys"
redactedKeyCount = "redaction.redacted.count"
maskedValues = "redaction.masked.keys"
maskedValueCount = "redaction.masked.count"
ignoredKeyCount = "redaction.ignored.count"
debug = "debug"
info = "info"
redactedKeys = "redaction.redacted.keys"
redactedKeyCount = "redaction.redacted.count"
maskedValues = "redaction.masked.keys"
maskedValueCount = "redaction.masked.count"
allowedValues = "redaction.allowed.keys"
allowedValueCount = "redaction.allowed.count"
ignoredKeyCount = "redaction.ignored.count"
)

// makeAllowList sets up a lookup table of allowed span attribute keys
Expand Down Expand Up @@ -282,16 +304,16 @@ func makeIgnoreList(c *Config) map[string]string {
return ignoreList
}

// makeBlockRegexList precompiles all the blocked regex patterns
func makeBlockRegexList(_ context.Context, config *Config) (map[string]*regexp.Regexp, error) {
blockRegexList := make(map[string]*regexp.Regexp, len(config.BlockedValues))
for _, pattern := range config.BlockedValues {
// makeRegexList precompiles all the regex patterns in the defined list
func makeRegexList(_ context.Context, valuesList []string) (map[string]*regexp.Regexp, error) {
regexList := make(map[string]*regexp.Regexp, len(valuesList))
for _, pattern := range valuesList {
re, err := regexp.Compile(pattern)
if err != nil {
// TODO: Placeholder for an error metric in the next PR
return nil, fmt.Errorf("error compiling regex in block list: %w", err)
return nil, fmt.Errorf("error compiling regex in list: %w", err)
}
blockRegexList[pattern] = re
regexList[pattern] = re
}
return blockRegexList, nil
return regexList, nil
}
Loading

0 comments on commit 1522978

Please sign in to comment.