1
0
forked from baron/baron-sso
Files
baron-sso/backend/internal/logger/client_log_policy.go
2026-04-01 20:32:09 +09:00

143 lines
3.9 KiB
Go

package logger
import (
"log/slog"
"regexp"
"strings"
)
var sensitiveClientLogKeys = map[string]struct{}{
"password": {},
"currentpassword": {},
"newpassword": {},
"oldpassword": {},
"token": {},
"accesstoken": {},
"refreshtoken": {},
"secret": {},
"clientsecret": {},
"authorization": {},
"cookie": {},
"setcookie": {},
"verificationcode": {},
"code": {},
"loginchallenge": {},
"loginverifier": {},
"sessionjwt": {},
"accessjwt": {},
"refreshjwt": {},
}
var (
logJSONSensitivePattern = regexp.MustCompile(`(?i)"(password|currentpassword|newpassword|oldpassword|token|accesstoken|refreshtoken|secret|clientsecret|authorization|cookie|setcookie|verificationcode|code|loginchallenge|loginverifier|sessionjwt|accessjwt|refreshjwt)"\s*:\s*"[^"]*"`)
logKVPattern = regexp.MustCompile(`(?i)\b(password|current_password|currentpassword|new_password|newpassword|old_password|oldpassword|token|access_token|accesstoken|refresh_token|refreshtoken|authorization|cookie|session_jwt|sessionjwt|access_jwt|accessjwt|refresh_jwt|refreshjwt)\b\s*[:=]\s*([^\s,;]+)`)
)
func IsProductionEnv(appEnv string) bool {
return IsProductionLikeEnv(appEnv)
}
func parseBoolFlag(raw string) bool {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "1", "true", "yes", "y", "on":
return true
default:
return false
}
}
func ClientDebugEnabled(appEnv, productionDebugFlag string) bool {
if !IsProductionEnv(appEnv) {
return true
}
return parseBoolFlag(productionDebugFlag)
}
func NormalizeClientLogLevel(level string) slog.Level {
switch strings.ToUpper(strings.TrimSpace(level)) {
case "SEVERE", "ERROR":
return slog.LevelError
case "WARNING", "WARN":
return slog.LevelWarn
case "DEBUG", "FINE", "TRACE":
return slog.LevelDebug
default:
return slog.LevelInfo
}
}
func ShouldAcceptClientLog(appEnv, productionDebugFlag, level string) bool {
if ClientDebugEnabled(appEnv, productionDebugFlag) {
return true
}
return NormalizeClientLogLevel(level) >= slog.LevelWarn
}
func ShouldFilterNoisyClientInfo(appEnv, productionDebugFlag, message string) bool {
if ClientDebugEnabled(appEnv, productionDebugFlag) {
return false
}
msg := strings.ToLower(message)
return strings.Contains(msg, "navigating to") ||
strings.Contains(msg, "going to") ||
strings.Contains(msg, "redirecting to") ||
strings.Contains(msg, "full paths for routes")
}
func SanitizeClientLogMessage(message string) string {
if strings.TrimSpace(message) == "" {
return message
}
sanitized := logJSONSensitivePattern.ReplaceAllStringFunc(message, func(segment string) string {
parts := strings.SplitN(segment, ":", 2)
if len(parts) != 2 {
return segment
}
return parts[0] + `:"*****"`
})
sanitized = logKVPattern.ReplaceAllString(sanitized, `$1=*****`)
return sanitized
}
func SanitizeClientLogData(data map[string]interface{}) map[string]interface{} {
if len(data) == 0 {
return data
}
out := make(map[string]interface{}, len(data))
for k, v := range data {
if isSensitiveClientLogKey(k) {
out[k] = "*****"
continue
}
out[k] = sanitizeClientLogValue(v)
}
return out
}
func sanitizeClientLogValue(v interface{}) interface{} {
switch val := v.(type) {
case map[string]interface{}:
return SanitizeClientLogData(val)
case []interface{}:
next := make([]interface{}, len(val))
for i := range val {
next[i] = sanitizeClientLogValue(val[i])
}
return next
case string:
return SanitizeClientLogMessage(val)
default:
return val
}
}
func isSensitiveClientLogKey(key string) bool {
normalized := strings.ToLower(strings.TrimSpace(key))
normalized = strings.ReplaceAll(normalized, "-", "")
normalized = strings.ReplaceAll(normalized, "_", "")
normalized = strings.ReplaceAll(normalized, ".", "")
normalized = strings.ReplaceAll(normalized, " ", "")
_, ok := sensitiveClientLogKeys[normalized]
return ok
}