ipn/ipnlocal: simplify redactErr
Use multierr.Range to iterate through an error tree
instead of multiple invocations of errors.As.
This scales better as we add more Go error types to the switch.
"tailscale.com/net/netutil"
"tailscale.com/tailcfg"
"tailscale.com/util/clientmetric"
+ "tailscale.com/util/multierr"
"tailscale.com/util/strs"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
@@ -365,58 +366,45 @@ func redactString(s string) string {
return string(b)
}
-func redactErr(err error) error {
+func redactErr(root error) error {
+ // redactStrings is a list of sensitive strings that were redacted.
+ // It is not sufficient to just snub out sensitive fields in Go errors
+ // since some wrapper errors like fmt.Errorf pre-cache the error string,
+ // which would unfortunately remain unaffected.
var redactStrings []string
- var pe *os.PathError
- if errors.As(err, &pe) {
- // If this is the root error, then we can redact it directly.
- if err == pe {
- pe.Path = redactString(pe.Path)
- return pe
- }
-
- // Otherwise, we have a *PathError somewhere in the error
- // chain, and we can't redact it because something later in the
- // chain may have cached the Error() return already (as
- // fmt.Errorf does).
- //
- // Add this path to the set of paths that we will redact, below.
- redactStrings = append(redactStrings, pe.Path)
-
- // Also redact the Path value so that anything that calls
- // Unwrap in the future gets the redacted value.
- pe.Path = redactString(pe.Path)
- }
-
- var le *os.LinkError
- if errors.As(err, &le) {
- // If this is the root error, then we can redact it directly.
- if err == le {
- le.New = redactString(le.New)
- le.Old = redactString(le.Old)
- return le
- }
-
- // As above
- redactStrings = append(redactStrings, le.New, le.Old)
- le.New = redactString(le.New)
- le.Old = redactString(le.Old)
- }
-
- if len(redactStrings) == 0 {
- return err
+ // Redact sensitive fields in known Go error types.
+ var unknownErrors int
+ multierr.Range(root, func(err error) bool {
+ switch err := err.(type) {
+ case *os.PathError:
+ redactStrings = append(redactStrings, err.Path)
+ err.Path = redactString(err.Path)
+ case *os.LinkError:
+ redactStrings = append(redactStrings, err.New, err.Old)
+ err.New = redactString(err.New)
+ err.Old = redactString(err.Old)
+ default:
+ unknownErrors++
}
+ return true
+ })
- s := err.Error()
- for _, toRedact := range redactStrings {
- s = strings.Replace(s, toRedact, redactString(toRedact), -1)
+ // If there are no redacted strings or no unknown error types,
+ // then we can return the possibly modified root error verbatim.
+ // Otherwise, we must replace redacted strings from any wrappers.
+ if len(redactStrings) == 0 || unknownErrors == 0 {
+ return root
}
- // Stringify and and replace any paths that we found above, then return
+ // Stringify and replace any paths that we found above, then return
// the error wrapped in a type that uses the newly-redacted string
// while also allowing Unwrap()-ing to the inner error type(s).
- return &redactedErr{msg: s, inner: err}
+ s := root.Error()
+ for _, toRedact := range redactStrings {
+ s = strings.ReplaceAll(s, toRedact, redactString(toRedact))
+ }
+ return &redactedErr{msg: s, inner: root}
}
func touchFile(path string) error {
