mirror of
https://github.com/komari-monitor/komari.git
synced 2025-10-23 03:31:56 +00:00
feat: 添加数据掩码功能以保护敏感信息
This commit is contained in:
@@ -70,7 +70,7 @@ func (g *Generic) OnCallback(ctx *gin.Context, state string, query map[string]st
|
|||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return factory.OidcCallback{}, fmt.Errorf("failed to get access token: %v", err)
|
return factory.OidcCallback{}, fmt.Errorf("failed to get access token: %s", utils.DataMasking(err.Error(), []string{g.Addition.ClientSecret, g.Addition.ClientId}))
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ func (g *Generic) OnCallback(ctx *gin.Context, state string, query map[string]st
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
||||||
return factory.OidcCallback{}, fmt.Errorf("failed to parse access token response: %v", err)
|
return factory.OidcCallback{}, fmt.Errorf("failed to parse access token response: %s", utils.DataMasking(err.Error(), []string{g.Addition.ClientSecret, g.Addition.ClientId}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
|
@@ -71,7 +71,7 @@ func (g *Github) OnCallback(ctx *gin.Context, state string, query map[string]str
|
|||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return factory.OidcCallback{}, fmt.Errorf("failed to get access token: %v", err)
|
return factory.OidcCallback{}, fmt.Errorf("failed to get access token: %s", utils.DataMasking(err.Error(), []string{g.Addition.ClientSecret, g.Addition.ClientId}))
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ func (g *Github) OnCallback(ctx *gin.Context, state string, query map[string]str
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
||||||
return factory.OidcCallback{}, fmt.Errorf("failed to parse access token response: %v", err)
|
return factory.OidcCallback{}, fmt.Errorf("failed to parse access token response: %s", utils.DataMasking(err.Error(), []string{g.Addition.ClientSecret, g.Addition.ClientId}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
|
151
utils/utils.go
151
utils/utils.go
@@ -2,6 +2,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/komari-monitor/komari/common"
|
"github.com/komari-monitor/komari/common"
|
||||||
@@ -118,3 +119,153 @@ func AverageReport(uuid string, time time.Time, records []common.Report, topPerc
|
|||||||
}
|
}
|
||||||
return newRecord
|
return newRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DataMasking(str string, private []string) string {
|
||||||
|
if str == "" || len(private) == 0 {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
mask := "********"
|
||||||
|
|
||||||
|
// 相似度阈值,可根据需要调节(0~1,越大越严格)
|
||||||
|
const threshold = 0.8
|
||||||
|
|
||||||
|
runes := []rune(str)
|
||||||
|
n := len(runes)
|
||||||
|
toMask := make([]bool, n)
|
||||||
|
|
||||||
|
// 预处理 private 中的词,去掉空、重复
|
||||||
|
uniq := make(map[string]struct{})
|
||||||
|
var words []string
|
||||||
|
for _, w := range private {
|
||||||
|
w = strings.TrimSpace(w)
|
||||||
|
if w == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := uniq[w]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
uniq[w] = struct{}{}
|
||||||
|
words = append(words, w)
|
||||||
|
}
|
||||||
|
if len(words) == 0 {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐词进行滑动窗口匹配 + 模糊匹配(Levenshtein 相似度)
|
||||||
|
for _, w := range words {
|
||||||
|
wRunes := []rune(w)
|
||||||
|
wl := len(wRunes)
|
||||||
|
if wl == 0 || wl > n {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滑动窗口大小采用敏感词长度
|
||||||
|
for i := 0; i <= n-wl; i++ {
|
||||||
|
if allMasked(toMask[i : i+wl]) { // 已全被标记则跳过
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sub := string(runes[i : i+wl])
|
||||||
|
sim := similarity(sub, w)
|
||||||
|
if sim >= threshold {
|
||||||
|
for k := 0; k < wl; k++ {
|
||||||
|
toMask[i+k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造输出:连续的掩码段只输出一次;如果原始被遮蔽长度>5,展示首尾字符
|
||||||
|
var b strings.Builder
|
||||||
|
i := 0
|
||||||
|
for i < n {
|
||||||
|
if toMask[i] {
|
||||||
|
start := i
|
||||||
|
for i < n && toMask[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
end := i // 不包含
|
||||||
|
segLen := end - start
|
||||||
|
if segLen > 5 {
|
||||||
|
b.WriteRune(runes[start])
|
||||||
|
b.WriteString(mask)
|
||||||
|
b.WriteRune(runes[end-1])
|
||||||
|
} else {
|
||||||
|
b.WriteString(mask)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.WriteRune(runes[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// allMasked 判断一个区间是否全部已经被标记
|
||||||
|
func allMasked(bools []bool) bool {
|
||||||
|
for _, v := range bools {
|
||||||
|
if !v {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// similarity 返回两个字符串的相似度 (0~1),基于 Levenshtein 距离
|
||||||
|
func similarity(a, b string) float64 {
|
||||||
|
if a == b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
ar := []rune(a)
|
||||||
|
br := []rune(b)
|
||||||
|
dist := levenshtein(ar, br)
|
||||||
|
maxLen := len(ar)
|
||||||
|
if len(br) > maxLen {
|
||||||
|
maxLen = len(br)
|
||||||
|
}
|
||||||
|
if maxLen == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 1 - float64(dist)/float64(maxLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// levenshtein 计算两个 rune slice 的编辑距离
|
||||||
|
func levenshtein(a, b []rune) int {
|
||||||
|
la, lb := len(a), len(b)
|
||||||
|
if la == 0 {
|
||||||
|
return lb
|
||||||
|
}
|
||||||
|
if lb == 0 {
|
||||||
|
return la
|
||||||
|
}
|
||||||
|
// 使用滚动数组降低空间复杂度
|
||||||
|
prev := make([]int, lb+1)
|
||||||
|
curr := make([]int, lb+1)
|
||||||
|
for j := 0; j <= lb; j++ {
|
||||||
|
prev[j] = j
|
||||||
|
}
|
||||||
|
for i := 1; i <= la; i++ {
|
||||||
|
curr[0] = i
|
||||||
|
for j := 1; j <= lb; j++ {
|
||||||
|
cost := 0
|
||||||
|
if a[i-1] != b[j-1] {
|
||||||
|
cost = 1
|
||||||
|
}
|
||||||
|
del := prev[j] + 1
|
||||||
|
ins := curr[j-1] + 1
|
||||||
|
sub := prev[j-1] + cost
|
||||||
|
curr[j] = minInt(del, ins, sub)
|
||||||
|
}
|
||||||
|
prev, curr = curr, prev
|
||||||
|
}
|
||||||
|
return prev[lb]
|
||||||
|
}
|
||||||
|
|
||||||
|
func minInt(vals ...int) int {
|
||||||
|
m := vals[0]
|
||||||
|
for _, v := range vals[1:] {
|
||||||
|
if v < m {
|
||||||
|
m = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user