diff --git a/go.mod b/go.mod index 68fb4cecf343267af1aa5f195d8b89a317909125..b976fa9abe37d4142fecf9f60790fa07245ebaee 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils -go 1.19 +go 1.21 require ( github.com/MindscapeHQ/raygun4go v1.1.1 diff --git a/handler_utils/request.go b/handler_utils/request.go index 8d251bf0739e0abb77c65c5732390b2005695ba4..ac13d59ee7dacbb6d98c2110c7f13d9bc91a5240 100644 --- a/handler_utils/request.go +++ b/handler_utils/request.go @@ -15,7 +15,7 @@ import ( "github.com/aws/aws-lambda-go/lambdacontext" ) -const HTTPXRequestIDHeaderValue = "X-Request-ID" +const HTTPXRequestIDHeaderKey = "X-Request-ID" func RequestIDFromLambdaContext(ctx context.Context) *string { // Get request ID from context @@ -39,14 +39,14 @@ func RequestIDFromHeaders(headers map[string]string, requestIDHeaderKey string) func AddRequestIDToHeaders(requestID *string, responseHeaders map[string]string, requestIDHeaderKey string, requestHeaders map[string]string) { if requestID != nil && responseHeaders != nil { responseHeaders[requestIDHeaderKey] = *requestID - responseHeaders[HTTPXRequestIDHeaderValue] = *requestID + responseHeaders[HTTPXRequestIDHeaderKey] = *requestID } // Add the HTTP X-Request-ID request header to the response headers: https://http.dev/x-request-id for key, val := range requestHeaders { // Don't be case-sensitive - if strings.ToLower(key) == strings.ToLower(HTTPXRequestIDHeaderValue) { - responseHeaders[key] = val + if strings.ToLower(key) == strings.ToLower(HTTPXRequestIDHeaderKey) { + responseHeaders[HTTPXRequestIDHeaderKey] = val break } } diff --git a/logs/database_logs.go b/logs/database_logs.go index cec7db69f657f47c5001a0b07a088d857ccf62c5..3edece0ccb41b957c1c3f69e31a46700d9f4a4ad 100644 --- a/logs/database_logs.go +++ b/logs/database_logs.go @@ -31,7 +31,8 @@ var ignoredTableInserts = []string{ } type QueryHook struct { - Debug bool + IgnoredTableInserts []string + Debug bool } func (d QueryHook) BeforeQuery(ctx context.Context, _ *bun.QueryEvent) context.Context { @@ -47,8 +48,9 @@ func (d QueryHook) AfterQuery(_ context.Context, event *bun.QueryEvent) { shouldLogQuery := !strings.Contains(sqlQuery, "api_key") // Don't log queries for certain tables + ignoredTableInsertsCombined := append(ignoredTableInserts, d.IgnoredTableInserts...) tableName := TableNameForQuery(event) - if lo.Contains(ignoredTableInserts, tableName) { + if lo.Contains(ignoredTableInsertsCombined, tableName) { shouldLogQuery = false } diff --git a/logs/logs.go b/logs/logs.go index 99aa65e5f6179e2d8643a9b6797d3ac9a7d515c2..0cb605d113ea7e900fb908ffe85549b82492d434 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -30,33 +30,32 @@ var build string var raygunClient *raygun4go.Client // Password filtering -var passwordRegex = regexp.MustCompile(`(?i:\\?"password\\?"\s*:\s*\\?"(.*)\\?",).*`) +var passwordRegex = regexp.MustCompile(`(?i:\\?"password\\?"\s*:\s*\\?"(.*)\\?").*`) +var byteArrayRegex = regexp.MustCompile(`(?i:\\?"(?i:[\w]*)(?i:byte|data)(?i:[\w]*)\\?"\s*:\s*\[([\d\s,]+)*\])`) func SanitiseLogs(logString string) string { + var isValidJsonString bool + isValidJsonString, logString = string_utils.PrettyJSON(logString) + if !isValidJsonString { + return logString + } + + logString = MaskByteArraysInJsonString(logString) logString = MaskPasswordsInJsonString(logString) return logString } -// MaskPasswordsInJsonString takes a string and, if it is a JSON string, sanitises all the password. In order for the -// regex to work correctly we need to prettify the JSON, so the function always returns a formatted JSON string. +// MaskPasswordsInJsonString takes a string and sanitises all the instances of fields named password. +// E.g. "{"password": "xyz123"}" will become "{"password": "***"}" func MaskPasswordsInJsonString(jsonString string) string { - var isValidJsonString bool - isValidJsonString, jsonString = string_utils.PrettyJSON(jsonString) - if !isValidJsonString { - return jsonString - } - - if passwordRegex.MatchString(jsonString) { - result := passwordRegex.FindAllStringSubmatch(jsonString, -1) - for _, match := range result { - if len(match) > 1 { - jsonString = strings.ReplaceAll(jsonString, match[1], "***") - } - } - } + return string_utils.ReplaceAllRegexStringSubmatch(passwordRegex, jsonString, "***") +} - return jsonString +// MaskByteArraysInJsonString takes a string and truncates all the instances of number array fields have the word +// "byte" in the name. E.g. {"file_bytes": [123,68,103]} will become "{"file_bytes": [...]}" +func MaskByteArraysInJsonString(jsonString string) string { + return string_utils.ReplaceAllRegexStringSubmatch(byteArrayRegex, jsonString, "...") } func SanitiseFields(fields map[string]interface{}) map[string]interface{} { diff --git a/string_utils/string_utils.go b/string_utils/string_utils.go index 39568207e484d3bd11862b5b6138fba71a46a86b..efa87313a72cf85fd3838ed6ae93fe7dc538646f 100644 --- a/string_utils/string_utils.go +++ b/string_utils/string_utils.go @@ -16,7 +16,14 @@ import ( "golang.org/x/text/unicode/norm" ) -const snakeCasePattern = `[a-z]([a-z0-9_]*[a-z0-9])*` +const ( + snakeCasePattern = `[a-z]([a-z0-9_]*[a-z0-9])*` + + regexIndexMatchStart = 0 + regexIndexMatchEnd = 1 + regexIndexSubmatchStart = 2 + regexIndexSubmatchEnd = 3 +) var snakeCaseRegex = regexp.MustCompile("^" + snakeCasePattern + "$") @@ -49,6 +56,29 @@ func ReplaceCaseInsensitive(string, toReplace, replaceWith string) string { return regex.ReplaceAllString(string, replaceWith) } +// ReplaceAllRegexStringSubmatch finds the submatches for a regular expression that has a single capturing group and +// replaces all the submatches (i.e. the part that matches the capturing group) with replaceWith. +// E.g. the regular expression re = a(x*)b captures any number of x's that are between an a and b. +// ReplaceAllRegexStringSubmatch(re, "-axxb-ab-axb-x-ax-xb-ba-", "?") will result in "-a?b-a?b-a?b-x-ax-xb-ba-" +func ReplaceAllRegexStringSubmatch(re *regexp.Regexp, s string, replaceWith string) string { + result := "" + lastIndex := 0 + + for _, v := range re.FindAllSubmatchIndex([]byte(s), -1) { + if len(v) == regexIndexSubmatchEnd+1 { + // One submatch - replace the submatch with replaceWith + result += s[lastIndex:v[regexIndexSubmatchStart]] + replaceWith + s[v[regexIndexSubmatchEnd]:v[regexIndexMatchEnd]] + lastIndex = v[regexIndexMatchEnd] + } else { + // A normal match with no submatch - don't replace anything (this should not really happen) + result += s[lastIndex:v[regexIndexMatchEnd]] + } + lastIndex = v[regexIndexMatchEnd] + } + + return result + s[lastIndex:] +} + // TrimQuotes - trims quotes from a string (ie: "foo" will return foo) func TrimQuotes(stringToTrim string) string { if len(stringToTrim) >= 2 {