diff --git a/results/telemetry.go b/results/telemetry.go index f43f0fe..44a1197 100644 --- a/results/telemetry.go +++ b/results/telemetry.go @@ -44,7 +44,7 @@ var fontMediumBytes []byte var fontLightBytes []byte var ( - ipv4Regex = regexp.MustCompile(`(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`) + ipv4Regex = regexp.MustCompile(`(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`) ipv6Regex = regexp.MustCompile(`(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))`) hostnameRegex = regexp.MustCompile(`"hostname":"([^\\\\"]|\\\\")*"`) @@ -164,13 +164,13 @@ func Record(w http.ResponseWriter, r *http.Request) { extra := r.FormValue("extra") if config.LoadedConfig().RedactIP { - ipAddr = "0.0.0.0" - ipv4Regex.ReplaceAllString(ispInfo, "0.0.0.0") - ipv4Regex.ReplaceAllString(logs, "0.0.0.0") - ipv6Regex.ReplaceAllString(ispInfo, "0.0.0.0") - ipv6Regex.ReplaceAllString(logs, "0.0.0.0") - hostnameRegex.ReplaceAllString(ispInfo, `"hostname":"REDACTED"`) - hostnameRegex.ReplaceAllString(logs, `"hostname":"REDACTED"`) + ipAddr = "0.0.0.0" + ispInfo = ipv4Regex.ReplaceAllString(ispInfo, "0.0.0.0") + logs = ipv4Regex.ReplaceAllString(logs, "0.0.0.0") + ispInfo = ipv6Regex.ReplaceAllString(ispInfo, "::") + logs = ipv6Regex.ReplaceAllString(logs, "::") + ispInfo = hostnameRegex.ReplaceAllString(ispInfo, `"hostname":"REDACTED"`) + logs = hostnameRegex.ReplaceAllString(logs, `"hostname":"REDACTED"`) } var record schema.TelemetryData diff --git a/results/telemetry_test.go b/results/telemetry_test.go new file mode 100644 index 0000000..d31ae95 --- /dev/null +++ b/results/telemetry_test.go @@ -0,0 +1,121 @@ +package results + +import ( + "testing" +) + +func TestIPRedaction(t *testing.T) { + tests := []struct { + name string + input string + wantOut string + regex func(string, string) string + redactTo string + }{ + { + name: "IPv4 in ispInfo is redacted", + input: `{"ip":"203.0.113.42","org":"AS12345 Example ISP"}`, + wantOut: `{"ip":"0.0.0.0","org":"AS12345 Example ISP"}`, + regex: ipv4Regex.ReplaceAllString, + redactTo: "0.0.0.0", + }, + { + name: "multiple IPv4 addresses in logs are all redacted", + input: `connected from 203.0.113.42, forwarded for 198.51.100.7`, + wantOut: `connected from 0.0.0.0, forwarded for 0.0.0.0`, + regex: ipv4Regex.ReplaceAllString, + redactTo: "0.0.0.0", + }, + { + name: "empty string is handled safely by IPv4 regex", + input: "", + wantOut: "", + regex: ipv4Regex.ReplaceAllString, + redactTo: "0.0.0.0", + }, + { + name: "IPv6 in ispInfo is redacted", + input: `{"ip":"2001:0db8:85a3:0000:0000:8a2e:0370:7334","org":"AS12345 Example ISP"}`, + wantOut: `{"ip":"::","org":"AS12345 Example ISP"}`, + regex: ipv6Regex.ReplaceAllString, + redactTo: "::", + }, + { + name: "empty string is handled safely by IPv6 regex", + input: "", + wantOut: "", + regex: ipv6Regex.ReplaceAllString, + redactTo: "::", + }, + { + name: "hostname in ispInfo is redacted", + input: `{"ip":"0.0.0.0","hostname":"client.example.com","org":"AS12345 Example ISP"}`, + wantOut: `{"ip":"0.0.0.0","hostname":"REDACTED","org":"AS12345 Example ISP"}`, + regex: hostnameRegex.ReplaceAllString, + redactTo: `"hostname":"REDACTED"`, + }, + { + name: "empty string is handled safely by hostname regex", + input: "", + wantOut: "", + regex: hostnameRegex.ReplaceAllString, + redactTo: `"hostname":"REDACTED"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.regex(tt.input, tt.redactTo) + if got != tt.wantOut { + t.Errorf("\ngot: %s\nwant: %s", got, tt.wantOut) + } + }) + } +} + +func TestIPRedactionChain(t *testing.T) { + tests := []struct { + name string + ispInfo string + logs string + wantISP string + wantLogs string + }{ + { + name: "IPv4 client: addresses and hostname redacted", + ispInfo: `{"ip":"203.0.113.42","hostname":"client.example.com","org":"AS12345 Example ISP"}`, + logs: `connected from 203.0.113.42 and 198.51.100.7`, + wantISP: `{"ip":"0.0.0.0","hostname":"REDACTED","org":"AS12345 Example ISP"}`, + wantLogs: `connected from 0.0.0.0 and 0.0.0.0`, + }, + { + name: "IPv6 client: redacted as :: to preserve address family for debugging", + ispInfo: `{"ip":"2001:0db8:85a3:0000:0000:8a2e:0370:7334","hostname":"client.example.com","org":"AS12345 Example ISP"}`, + logs: `connected from 2001:0db8:85a3:0000:0000:8a2e:0370:7334`, + wantISP: `{"ip":"::","hostname":"REDACTED","org":"AS12345 Example ISP"}`, + wantLogs: `connected from ::`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ispInfo := tt.ispInfo + logs := tt.logs + + // Mirror the full redaction block in Record() + ispInfo = ipv4Regex.ReplaceAllString(ispInfo, "0.0.0.0") + logs = ipv4Regex.ReplaceAllString(logs, "0.0.0.0") + ispInfo = ipv6Regex.ReplaceAllString(ispInfo, "::") + logs = ipv6Regex.ReplaceAllString(logs, "::") + ispInfo = hostnameRegex.ReplaceAllString(ispInfo, `"hostname":"REDACTED"`) + logs = hostnameRegex.ReplaceAllString(logs, `"hostname":"REDACTED"`) + + if ispInfo != tt.wantISP { + t.Errorf("ispInfo\ngot: %s\nwant: %s", ispInfo, tt.wantISP) + } + if logs != tt.wantLogs { + t.Errorf("logs\ngot: %s\nwant: %s", logs, tt.wantLogs) + } + }) + } +}