Skip to content

Commit 71e8073

Browse files
wardab0104Ward Miao
andauthored
feat: support to mask multiple values in query and json (#91)
Co-authored-by: Ward Miao <[email protected]>
1 parent a62e53b commit 71e8073

File tree

2 files changed

+87
-14
lines changed

2 files changed

+87
-14
lines changed

pkg/logger/log/maskutil.go

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type FieldRegex struct {
4242
func (f *FieldRegex) InitFieldRegex(fieldName string) {
4343
f.FieldName = fieldName
4444
// "fieldName":"(.*?)"
45-
f.JsonPattern = regexp.MustCompile(fmt.Sprintf("\"%s\":\"(.*?)\"", fieldName))
45+
f.JsonPattern = regexp.MustCompile(fmt.Sprintf("\"%s\":(\\[.*?\\]|\".*?\")", fieldName))
4646
// fieldName=(.*?[^&]*)|fieldName=(.*?)$
4747
f.QueryStringPattern = regexp.MustCompile(fmt.Sprintf("%s=(.*?[^&]*)|%s=(.*?)$", fieldName, fieldName))
4848
}
@@ -126,7 +126,21 @@ func MaskPIIFields(contentType, content, fields string) string {
126126
if strings.Contains(contentType, "application/json") {
127127
content = fieldRegex.JsonPattern.ReplaceAllStringFunc(content, func(m string) string {
128128
parts := fieldRegex.JsonPattern.FindStringSubmatch(m)
129-
return fmt.Sprintf(`"%s":"%s"`, fieldName, maskLastNChars(parts[1], defaultMaskCharsCount))
129+
if len(parts) < 2 {
130+
return m
131+
}
132+
matched := parts[1]
133+
if strings.HasPrefix(matched, "[") {
134+
matched = strings.Trim(matched, "[]")
135+
items := strings.Split(matched, ",")
136+
for i, item := range items {
137+
item = strings.TrimSpace(item)
138+
item = strings.Trim(item, "\"")
139+
items[i] = fmt.Sprintf("\"%s\"", maskLastNChars(item, defaultMaskCharsCount))
140+
}
141+
return fmt.Sprintf(`"%s":[%s]`, fieldName, strings.Join(items, ","))
142+
}
143+
return fmt.Sprintf(`"%s":"%s"`, fieldName, maskLastNChars(matched, defaultMaskCharsCount))
130144
})
131145
} else if strings.Contains(contentType, "application/x-www-form-urlencoded") {
132146
content = fieldRegex.QueryStringPattern.ReplaceAllStringFunc(content, func(m string) string {
@@ -141,7 +155,21 @@ func MaskPIIFields(contentType, content, fields string) string {
141155
if fieldRegex.JsonPattern.MatchString(content) {
142156
content = fieldRegex.JsonPattern.ReplaceAllStringFunc(content, func(m string) string {
143157
parts := fieldRegex.JsonPattern.FindStringSubmatch(m)
144-
return fmt.Sprintf(`"%s":"%s"`, fieldName, maskLastNChars(parts[1], defaultMaskCharsCount))
158+
if len(parts) < 2 {
159+
return m
160+
}
161+
matched := parts[1]
162+
if strings.HasPrefix(matched, "[") {
163+
matched = strings.Trim(matched, "[]")
164+
items := strings.Split(matched, ",")
165+
for i, item := range items {
166+
item = strings.TrimSpace(item)
167+
item = strings.Trim(item, "\"")
168+
items[i] = fmt.Sprintf("\"%s\"", maskLastNChars(item, defaultMaskCharsCount))
169+
}
170+
return fmt.Sprintf(`"%s":[%s]`, fieldName, strings.Join(items, ","))
171+
}
172+
return fmt.Sprintf(`"%s":"%s"`, fieldName, maskLastNChars(matched, defaultMaskCharsCount))
145173
})
146174
} else if fieldRegex.QueryStringPattern.MatchString(content) {
147175
content = fieldRegex.QueryStringPattern.ReplaceAllStringFunc(content, func(m string) string {
@@ -199,6 +227,31 @@ func maskLastNChars(value string, n int) string {
199227
return ""
200228
}
201229

230+
if strings.HasPrefix(value, `"`) {
231+
value = strings.Trim(value, `""`)
232+
}
233+
decoded, err := url.QueryUnescape(value)
234+
if err != nil {
235+
logrus.Warn("failed to unescape url parameter for masking sensitive data: ", err)
236+
} else {
237+
value = decoded
238+
}
239+
if strings.Contains(value, ",") {
240+
maskedValues := make([]string, 0)
241+
values := strings.Split(value, ",")
242+
for _, v := range values {
243+
maskedValues = append(maskedValues, mask(v, n))
244+
}
245+
return strings.Join(maskedValues, ",")
246+
} else {
247+
return mask(value, n)
248+
}
249+
}
250+
251+
func mask(value string, n int) string {
252+
if value == "" {
253+
return ""
254+
}
202255
if strings.Contains(value, "@") {
203256
parts := strings.Split(value, "@")
204257
if len(parts[0]) <= n {

pkg/logger/log/maskutil_test.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -401,9 +401,9 @@ func TestMaskPIIFieldsInQuery_MaskMultipleFields(t *testing.T) {
401401
inputAndExpected := [][]string{
402402
// Content-Type = application/json
403403
{
404-
"application/json", // content-type
405-
"{\"username\":\"username12\"}", // input
406-
"{\"username\":\"username****\"}", // expected
404+
"application/json", // content-type
405+
"{\"username\":\"username12\", \"emails\":[\"[email protected]\",\"[email protected]\"]}", // input
406+
"{\"username\":\"username****\", \"emails\":[\"tes****@accelbyte.net\",\"tes****@accelbyte.net\"]}", // expected
407407
},
408408
{
409409
"application/json",
@@ -459,8 +459,8 @@ func TestMaskPIIFieldsInQuery_MaskMultipleFields(t *testing.T) {
459459
// Content-Type = plain/text
460460
{
461461
"plain/text",
462-
"{\"username\":\"username12\",\"emailAddress\":\"[email protected]\",\"displayName\":\"My Display Name\"}",
463-
"{\"username\":\"username****\",\"emailAddress\":\"te****@accelbyte.net\",\"displayName\":\"My Display Name\"}",
462+
"{\"username\":\"username12\",\"emailAddress\":\"[email protected]\",\"displayName\":\"My Display Name\", \"emails\":[\"[email protected]\",\"[email protected]\"]}",
463+
"{\"username\":\"username****\",\"emailAddress\":\"te****@accelbyte.net\",\"displayName\":\"My Display Name\", \"emails\":[\"tes****@accelbyte.net\",\"tes****@accelbyte.net\"]}",
464464
},
465465
{
466466
"plain/text",
@@ -490,7 +490,7 @@ func TestMaskPIIFieldsInQuery_MaskMultipleFields(t *testing.T) {
490490
}
491491

492492
for _, val := range inputAndExpected {
493-
assert.Equal(t, val[2], MaskPIIFields(val[0], val[1], "username,emailAddress"))
493+
assert.Equal(t, val[2], MaskPIIFields(val[0], val[1], "username,emailAddress,emails"))
494494
}
495495
}
496496

@@ -742,11 +742,11 @@ func TestMaskPIIFields_ConcurrentCall(t *testing.T) {
742742
wg := sync.WaitGroup{}
743743
wg.Add(1)
744744
go func() {
745-
input := "{\"username\":\"username12\"}"
746-
expected := "{\"username\":\"username****\"}"
745+
input := "{\"username\":\"username12\", \"emails\":[\"[email protected]\",\"[email protected]\"]}"
746+
expected := "{\"username\":\"username****\", \"emails\":[\"tes****@accelbyte.net\",\"tes****@accelbyte.net\"]}"
747747
i := 0
748748
for i < 1000 {
749-
assert.Equal(t, expected, MaskPIIFields("application/json", input, "username"))
749+
assert.Equal(t, expected, MaskPIIFields("application/json", input, "username,emails"))
750750
i++
751751
}
752752
wg.Done()
@@ -801,10 +801,14 @@ func TestMaskMultiplePIIQueryParams(t *testing.T) {
801801
"https://example.net?username=user name12&password=mypassword 123&displayName=My Display Name",
802802
"https://example.net?username=user name****&password=mypassword 123&displayName=My Display Name",
803803
},
804+
{
805+
"https://[email protected],[email protected]&password=mypassword 123&displayName=My Display Name",
806+
"https://example.net?loginIds=tes****@accelbyte.net,tes****@accelbyte.net&password=mypassword 123&displayName=My Display Name",
807+
},
804808
}
805809

806810
for _, val := range inputAndExpected {
807-
assert.Equal(t, val[1], MaskPIIQueryParams(val[0], "username,emailAddress"))
811+
assert.Equal(t, val[1], MaskPIIQueryParams(val[0], "username,emailAddress,loginIds"))
808812
}
809813
}
810814

@@ -824,10 +828,14 @@ func TestMaskPIIQueryParamOfEncodedURLs(t *testing.T) {
824828
"https://example.net?openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.mode=id_res&openid.identity=https%3A%2F%2Fsteamcommunity.com%2Fopenid%2Fid%2F123456789&openid.signed=signed%2Cop_endpoint%2Cclaimed_id%2Cidentity&openid.sig=dGhpc19pc19zaWc%3D",
825829
"https://example.net?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=id_res&openid.identity=https://steamcommunity.com/openid/id/123456789&openid.signed=signed,op_endpoint,claimed_id,identity&openid.sig=dGhpc19pc19zaWc=",
826830
},
831+
{
832+
"https://example.net?loginIds=test1%40accelbyte.net,test2%40accelbyte.net&displayName=My%20Display%20Name",
833+
"https://example.net?loginIds=tes****@accelbyte.net,tes****@accelbyte.net&displayName=My Display Name",
834+
},
827835
}
828836

829837
for _, val := range inputAndExpected {
830-
assert.Equal(t, val[1], MaskPIIQueryParams(val[0], "username,emailAddress"))
838+
assert.Equal(t, val[1], MaskPIIQueryParams(val[0], "username,emailAddress,loginIds"))
831839
}
832840
}
833841

@@ -883,5 +891,17 @@ func TestMaskPIIQueryParam_ConcurrentCall(t *testing.T) {
883891
wg.Done()
884892
}()
885893

894+
wg.Add(1)
895+
go func() {
896+
input := "https://[email protected],[email protected]&key=12"
897+
expected := "https://example.net?loginIds=tes****@accelbyte.net,tes****@accelbyte.net&key=12"
898+
i := 0
899+
for i < 1000 {
900+
assert.Equal(t, expected, MaskPIIQueryParams(input, "loginIds"))
901+
i++
902+
}
903+
wg.Done()
904+
}()
905+
886906
wg.Wait()
887907
}

0 commit comments

Comments
 (0)