tailscale/logtail: redact public ipv6 and ipv4 ip addresses within tailscaled. (#10531)

Updates #15664

Signed-off-by: Anishka Singh <anishkasingh66@gmail.com>
This commit is contained in:
as2643 2023-12-15 15:15:49 -08:00 committed by GitHub
parent 3a635db06e
commit 3fb6ee7fdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 123 additions and 0 deletions

View File

@ -15,7 +15,9 @@ import (
"log" "log"
mrand "math/rand" mrand "math/rand"
"net/http" "net/http"
"net/netip"
"os" "os"
"regexp"
"strconv" "strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -24,6 +26,7 @@ import (
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/net/sockstats" "tailscale.com/net/sockstats"
"tailscale.com/net/tsaddr"
"tailscale.com/tstime" "tailscale.com/tstime"
tslogger "tailscale.com/types/logger" tslogger "tailscale.com/types/logger"
"tailscale.com/types/logid" "tailscale.com/types/logid"
@ -725,6 +728,8 @@ func (l *Logger) Logf(format string, args ...any) {
fmt.Fprintf(l, format, args...) fmt.Fprintf(l, format, args...)
} }
var obscureIPs = envknob.RegisterBool("TS_OBSCURE_LOGGED_IPS")
// Write logs an encoded JSON blob. // Write logs an encoded JSON blob.
// //
// If the []byte passed to Write is not an encoded JSON blob, // If the []byte passed to Write is not an encoded JSON blob,
@ -749,6 +754,10 @@ func (l *Logger) Write(buf []byte) (int, error) {
} }
} }
if obscureIPs() {
buf = redactIPs(buf)
}
l.writeLock.Lock() l.writeLock.Lock()
defer l.writeLock.Unlock() defer l.writeLock.Unlock()
@ -757,6 +766,40 @@ func (l *Logger) Write(buf []byte) (int, error) {
return inLen, err return inLen, err
} }
var (
regexMatchesIPv6 = regexp.MustCompile(`([0-9a-fA-F]{1,4}):([0-9a-fA-F]{1,4}):([0-9a-fA-F:]{1,4})*`)
regexMatchesIPv4 = regexp.MustCompile(`(\d{1,3})\.(\d{1,3})\.\d{1,3}\.\d{1,3}`)
)
// redactIPs is a helper function used in Write() to redact IPs (other than tailscale IPs).
// This function takes a log line as a byte slice and
// uses regex matching to parse and find IP addresses. Based on if the IP address is IPv4 or
// IPv6, it parses and replaces the end of the addresses with an "x". This function returns the
// log line with the IPs redacted.
func redactIPs(buf []byte) []byte {
out := regexMatchesIPv6.ReplaceAllFunc(buf, func(b []byte) []byte {
ip, err := netip.ParseAddr(string(b))
if err != nil || tsaddr.IsTailscaleIP(ip) {
return b // don't change this one
}
prefix := bytes.Split(b, []byte(":"))
return bytes.Join(append(prefix[:2], []byte("x")), []byte(":"))
})
out = regexMatchesIPv4.ReplaceAllFunc(out, func(b []byte) []byte {
ip, err := netip.ParseAddr(string(b))
if err != nil || tsaddr.IsTailscaleIP(ip) {
return b // don't change this one
}
prefix := bytes.Split(b, []byte("."))
return bytes.Join(append(prefix[:2], []byte("x.x")), []byte("."))
})
return []byte(out)
}
var ( var (
openBracketV = []byte("[v") openBracketV = []byte("[v")
v1 = []byte("[v1] ") v1 = []byte("[v1] ")

View File

@ -14,6 +14,7 @@ import (
"testing" "testing"
"time" "time"
"tailscale.com/envknob"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/tstime" "tailscale.com/tstime"
) )
@ -406,3 +407,82 @@ func TestLoggerWriteResult(t *testing.T) {
t.Errorf("mismatch.\n got: %#q\nwant: %#q", back, want) t.Errorf("mismatch.\n got: %#q\nwant: %#q", back, want)
} }
} }
func TestRedact(t *testing.T) {
envknob.Setenv("TS_OBSCURE_LOGGED_IPS", "true")
tests := []struct {
in string
want string
}{
// tests for ipv4 addresses
{
"120.100.30.47",
"120.100.x.x",
},
{
"192.167.0.1/65",
"192.167.x.x/65",
},
{
"node [5Btdd] d:e89a3384f526d251 now using 10.0.0.222:41641 mtu=1360 tx=d81a8a35a0ce",
"node [5Btdd] d:e89a3384f526d251 now using 10.0.x.x:41641 mtu=1360 tx=d81a8a35a0ce",
},
//tests for ipv6 addresses
{
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"2001:0db8:x",
},
{
"2345:0425:2CA1:0000:0000:0567:5673:23b5",
"2345:0425:x",
},
{
"2601:645:8200:edf0::c9de/64",
"2601:645:x/64",
},
{
"node [5Btdd] d:e89a3384f526d251 now using 2051:0000:140F::875B:131C mtu=1360 tx=d81a8a35a0ce",
"node [5Btdd] d:e89a3384f526d251 now using 2051:0000:x mtu=1360 tx=d81a8a35a0ce",
},
{
"2601:645:8200:edf0::c9de/64 2601:645:8200:edf0:1ce9:b17d:71f5:f6a3/64",
"2601:645:x/64 2601:645:x/64",
},
//tests for tailscale ip addresses
{
"100.64.5.6",
"100.64.5.6",
},
{
"fd7a:115c:a1e0::/96",
"fd7a:115c:a1e0::/96",
},
//tests for ipv6 and ipv4 together
{
"192.167.0.1 2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"192.167.x.x 2001:0db8:x",
},
{
"node [5Btdd] d:e89a3384f526d251 now using 10.0.0.222:41641 mtu=1360 tx=d81a8a35a0ce 2345:0425:2CA1::0567:5673:23b5",
"node [5Btdd] d:e89a3384f526d251 now using 10.0.x.x:41641 mtu=1360 tx=d81a8a35a0ce 2345:0425:x",
},
{
"100.64.5.6 2091:0db8:85a3:0000:0000:8a2e:0370:7334",
"100.64.5.6 2091:0db8:x",
},
{
"192.167.0.1 120.100.30.47 2041:0000:140F::875B:131B",
"192.167.x.x 120.100.x.x 2041:0000:x",
},
{
"fd7a:115c:a1e0::/96 192.167.0.1 2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"fd7a:115c:a1e0::/96 192.167.x.x 2001:0db8:x",
},
}
for _, tt := range tests {
gotBuf := redactIPs([]byte(tt.in))
if string(gotBuf) != tt.want {
t.Errorf("for %q,\n got: %#q\nwant: %#q\n", tt.in, gotBuf, tt.want)
}
}
}