-
Notifications
You must be signed in to change notification settings - Fork 360
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement a new field filter type, the refaction filter. Redaction filters use regular expressions to suppress sensitive information in string fields in Tetragon events. When a regular expression in a redcation filter matches a string, everything inside of its capture groups is replaced with `*****`, effectively censoring the output. For example, the regular expression `(?:--password|-p)(?:\s+|=)(\S*)` will convert the string "--password=foo" into "--password=*****". In some cases, it is not desirable to apply a redaction filter to all events. For this use case, redaction filters also include an event filter which can be used to select events to redact. This event filter is configured with the same syntax as an export filter. As a more concrete example: {"match": {"binary_regex": ["^foo$"]}, "redact": ["\W(qux)\W"]} The above filter would redact any occurrences of the word "qux" in events with the binary name "foo". Due to the sensitive nature of redaction, these filters are applied as configured in the agent, regardless of whether an event is exported via gRPC or the JSON exporter. In other words, redaction filter configuration always happens at the agent config level, not in the gRPC client CLI. Signed-off-by: William Findlay <[email protected]>
- Loading branch information
1 parent
2978456
commit 319340d
Showing
24 changed files
with
1,883 additions
and
379 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Authors of Tetragon | ||
|
||
package fieldfilters | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/cilium/tetragon/api/v1/tetragon" | ||
"github.com/cilium/tetragon/pkg/filters" | ||
v1 "github.com/cilium/tetragon/pkg/oldhubble/api/v1" | ||
hubbleFilters "github.com/cilium/tetragon/pkg/oldhubble/filters" | ||
"google.golang.org/protobuf/reflect/protopath" | ||
"google.golang.org/protobuf/reflect/protorange" | ||
"google.golang.org/protobuf/reflect/protoreflect" | ||
) | ||
|
||
const REDACTION_STR = "*****" | ||
|
||
type RedactionFilter struct { | ||
match hubbleFilters.FilterFuncs | ||
redact []*regexp.Regexp | ||
} | ||
|
||
func ParseRedactionFilterList(filters string) ([]*tetragon.RedactionFilter, error) { | ||
if filters == "" { | ||
return nil, nil | ||
} | ||
dec := json.NewDecoder(strings.NewReader(filters)) | ||
var results []*tetragon.RedactionFilter | ||
for { | ||
var result tetragon.RedactionFilter | ||
if err := dec.Decode(&result); err != nil { | ||
if err == io.EOF { | ||
break | ||
} | ||
return nil, fmt.Errorf("failed to parse redaction filter list: %w", err) | ||
} | ||
results = append(results, &result) | ||
} | ||
return results, nil | ||
} | ||
|
||
func RedactionFilterListFromProto(protoFilters []*tetragon.RedactionFilter) ([]*RedactionFilter, error) { | ||
var filters []*RedactionFilter | ||
for _, f := range protoFilters { | ||
filter, err := RedactionFilterFromProto(f) | ||
if err != nil { | ||
return nil, err | ||
} | ||
filters = append(filters, filter) | ||
} | ||
|
||
return filters, nil | ||
} | ||
|
||
// RedactionFilterFromProto constructs a new RedactionFilter from a Tetragon API redaction filter. | ||
func RedactionFilterFromProto(protoFilter *tetragon.RedactionFilter) (*RedactionFilter, error) { | ||
var err error | ||
filter := &RedactionFilter{} | ||
|
||
// Construct match funcs | ||
filter.match, err = filters.BuildFilterList(context.TODO(), protoFilter.Match, filters.Filters) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to construct match for redaction filter: %w", err) | ||
} | ||
|
||
if len(protoFilter.Redact) == 0 { | ||
return nil, fmt.Errorf("refusing to construct redaction filter with no redactions") | ||
} | ||
|
||
// Compile regex | ||
for _, re := range protoFilter.Redact { | ||
compiled, err := regexp.Compile(re) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to compile redaction regex `%s`: %w", re, err) | ||
} | ||
filter.redact = append(filter.redact, compiled) | ||
} | ||
|
||
return filter, nil | ||
} | ||
|
||
// Redact resursively checks any string fields in the event for matches to | ||
// redaction regexes and replaces any capture groups with `*****`. | ||
func (f *RedactionFilter) Redact(event *tetragon.GetEventsResponse) { | ||
if f.match != nil { | ||
if !f.match.MatchOne(&v1.Event{Event: event}) { | ||
return | ||
} | ||
} | ||
|
||
f.doRedact(event.ProtoReflect()) | ||
} | ||
|
||
func (f *RedactionFilter) doRedact(msg protoreflect.Message) { | ||
protorange.Range(msg, func(p protopath.Values) error { | ||
last := p.Index(-1) | ||
s, ok := last.Value.Interface().(string) | ||
if !ok { | ||
return nil | ||
} | ||
|
||
for _, re := range f.redact { | ||
s = redactString(re, s) | ||
} | ||
|
||
beforeLast := p.Index(-2) | ||
switch last.Step.Kind() { | ||
case protopath.FieldAccessStep: | ||
m := beforeLast.Value.Message() | ||
fd := last.Step.FieldDescriptor() | ||
m.Set(fd, protoreflect.ValueOfString(s)) | ||
case protopath.ListIndexStep: | ||
ls := beforeLast.Value.List() | ||
i := last.Step.ListIndex() | ||
ls.Set(i, protoreflect.ValueOfString(s)) | ||
case protopath.MapIndexStep: | ||
ms := beforeLast.Value.Map() | ||
k := last.Step.MapIndex() | ||
ms.Set(k, protoreflect.ValueOfString(s)) | ||
} | ||
|
||
return nil | ||
}) | ||
} | ||
|
||
func redactString(re *regexp.Regexp, s string) string { | ||
s = re.ReplaceAllStringFunc(s, func(s string) string { | ||
var redacted strings.Builder | ||
|
||
idx := re.FindStringSubmatchIndex(s) | ||
if len(idx) < 2 { | ||
return s | ||
} | ||
|
||
// Skip first idx pair which is entire string | ||
lastOffset := 0 | ||
for i := 2; i < len(idx); i += 2 { | ||
// Handle nested capture groups that have already been redacted | ||
if idx[i] < lastOffset { | ||
continue | ||
} | ||
redacted.WriteString(s[lastOffset:idx[i]]) | ||
redacted.WriteString(REDACTION_STR) | ||
lastOffset = idx[i+1] | ||
} | ||
// Write the rest of the string | ||
redacted.WriteString(s[lastOffset:]) | ||
|
||
return redacted.String() | ||
}) | ||
return s | ||
} |
Oops, something went wrong.