mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 02:17:25 +02:00
redactSecrets only looked at strings, maps and errors, so a marked secret inside a slice, struct or []byte field was logged as is, and a typed-nil error field made it panic. It now renders each field with fmt.Sprint, as the text formatter does (which also survives typed-nil errors), and writes []byte raw. Signed-off-by: Deluan <deluan@navidrome.org>
121 lines
2.8 KiB
Go
Executable file
121 lines
2.8 KiB
Go
Executable file
package log
|
|
|
|
// Copied from https://github.com/whuang8/redactrus (MIT License)
|
|
// Copyright (c) 2018 William Huang
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Hook is a logrus hook for redacting information from logs
|
|
type Hook struct {
|
|
// Messages with a log level not contained in this array
|
|
// will not be dispatched. If empty, all messages will be dispatched.
|
|
AcceptedLevels []logrus.Level
|
|
RedactionList []string
|
|
redactionKeys []*regexp.Regexp
|
|
}
|
|
|
|
// Levels returns the user defined AcceptedLevels
|
|
// If AcceptedLevels is empty, all logrus levels are returned
|
|
func (h *Hook) Levels() []logrus.Level {
|
|
if len(h.AcceptedLevels) == 0 {
|
|
return logrus.AllLevels
|
|
}
|
|
return h.AcceptedLevels
|
|
}
|
|
|
|
// Fire redacts values in a log Entry that match
|
|
// with keys defined in the RedactionList
|
|
func (h *Hook) Fire(e *logrus.Entry) error {
|
|
if err := h.initRedaction(); err != nil {
|
|
return err
|
|
}
|
|
redactSecrets(e)
|
|
for _, re := range h.redactionKeys {
|
|
// Redact based on key matching in Data fields
|
|
for k, v := range e.Data {
|
|
if re.MatchString(k) {
|
|
e.Data[k] = "[REDACTED]"
|
|
continue
|
|
}
|
|
if v == nil {
|
|
continue
|
|
}
|
|
switch reflect.TypeOf(v).Kind() {
|
|
case reflect.String:
|
|
// Via reflect: named string types (e.g. enums) have Kind String but fail v.(string).
|
|
e.Data[k] = re.ReplaceAllString(reflect.ValueOf(v).String(), "$1[REDACTED]$2")
|
|
continue
|
|
case reflect.Map:
|
|
s := fmt.Sprintf("%+v", v)
|
|
e.Data[k] = re.ReplaceAllString(s, "$1[REDACTED]$2")
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Redact based on text matching in the Message field
|
|
e.Message = re.ReplaceAllString(e.Message, "$1[REDACTED]$2")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// redactSecrets hides the values marked with WithSecrets in the context the entry was logged with.
|
|
func redactSecrets(e *logrus.Entry) {
|
|
secrets := secretsFrom(e.Context)
|
|
if len(secrets) == 0 {
|
|
return
|
|
}
|
|
hide := func(s string) string {
|
|
for _, secret := range secrets {
|
|
s = strings.ReplaceAll(s, secret, "[REDACTED]")
|
|
}
|
|
return s
|
|
}
|
|
e.Message = hide(e.Message)
|
|
for k, v := range e.Data {
|
|
if v == nil {
|
|
continue
|
|
}
|
|
// fmt.Sprint renders like the text formatter and survives typed-nil errors; []byte is written raw.
|
|
var s string
|
|
if b, ok := v.([]byte); ok {
|
|
s = string(b)
|
|
} else {
|
|
s = fmt.Sprint(v)
|
|
}
|
|
if hidden := hide(s); hidden != s {
|
|
e.Data[k] = hidden
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Hook) initRedaction() error {
|
|
if len(h.redactionKeys) == 0 {
|
|
for _, redactionKey := range h.RedactionList {
|
|
re, err := regexp.Compile(redactionKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h.redactionKeys = append(h.redactionKeys, re)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *Hook) redact(msg string) (string, error) {
|
|
if err := h.initRedaction(); err != nil {
|
|
return msg, err
|
|
}
|
|
for _, re := range h.redactionKeys {
|
|
msg = re.ReplaceAllString(msg, "$1[REDACTED]$2")
|
|
}
|
|
|
|
return msg, nil
|
|
}
|