92 lines
2.9 KiB
Go
92 lines
2.9 KiB
Go
package diagnostics
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
)
|
|
|
|
func TestExportPersistsOnlyAllowlistedFields(t *testing.T) {
|
|
t.Parallel()
|
|
var output bytes.Buffer
|
|
path := filepath.Join(t.TempDir(), "diagnostics.jsonl")
|
|
r := &Recorder{path: path, logger: slog.New(slog.NewJSONHandler(&output, nil))}
|
|
const secret = "password-token-cookie-SID-secret"
|
|
r.Record(Event{Code: "ip_fetch_failed", Target: "https://user:" + secret + "@private.test/?token=" + secret, Failure: secret, Address: secret, Family: secret, Network: &Network{Nameservers: []string{"127.0.0.11", secret}, DockerUpstreams: []string{secret}, Routes: []Route{{secret, secret, secret, secret}}}})
|
|
r.Record(Event{Code: secret, Target: secret})
|
|
r.Record(Event{Code: "ip_fetch_failed", Address: "fe80::1%" + secret, DNSResolver: "fe80::2%" + secret, Network: &Network{Nameservers: []string{"fe80::3%" + secret}}})
|
|
reloaded := &Recorder{path: path, logger: slog.New(slog.NewTextHandler(io.Discard, nil))}
|
|
encoded, err := json.Marshal(reloaded.Snapshot())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, data := range []string{string(encoded), string(file), output.String()} {
|
|
if strings.Contains(data, secret) || strings.Contains(data, "private.test") {
|
|
t.Fatalf("secret escaped allowlist: %s", data)
|
|
}
|
|
}
|
|
if !strings.Contains(string(encoded), "127.0.0.11") || !strings.Contains(string(encoded), "ip_fetch_failed") {
|
|
t.Fatal("technical evidence lost")
|
|
}
|
|
info, err := os.Stat(path)
|
|
if err != nil || info.Mode().Perm() != 0600 {
|
|
t.Fatalf("unsafe diagnostic permissions: %v %v", info, err)
|
|
}
|
|
}
|
|
|
|
func TestDiagnosticRotationIsBoundedAndReloadable(t *testing.T) {
|
|
t.Parallel()
|
|
path := filepath.Join(t.TempDir(), "diagnostics.jsonl")
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = file.Truncate(maxFileSize); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file.Close()
|
|
r := &Recorder{path: path, logger: slog.New(slog.NewTextHandler(io.Discard, nil))}
|
|
r.Record(Event{Code: "update_recovered", Attempt: 20})
|
|
info, err := os.Stat(path + ".1")
|
|
if err != nil || info.Size() != maxFileSize {
|
|
t.Fatalf("rotation: %v %v", info, err)
|
|
}
|
|
events := r.Snapshot()
|
|
if len(events) != 1 || events[0].Attempt != 20 {
|
|
t.Fatalf("new event lost after rotation: %#v", events)
|
|
}
|
|
}
|
|
|
|
func TestNetworkFailuresAndRouteDecoding(t *testing.T) {
|
|
t.Parallel()
|
|
for _, test := range []struct {
|
|
err error
|
|
want string
|
|
}{
|
|
{&net.DNSError{IsTemporary: true}, "dns_server_failure"},
|
|
{&net.DNSError{IsTimeout: true}, "dns_timeout"},
|
|
{syscall.EHOSTUNREACH, "no_route"},
|
|
{syscall.ECONNREFUSED, "connection_refused"},
|
|
{context.DeadlineExceeded, "timeout"},
|
|
} {
|
|
if got := Failure(test.err); got != test.want {
|
|
t.Errorf("failure=%s want %s", got, test.want)
|
|
}
|
|
}
|
|
if got := routeIP("010012AC"); got != "172.18.0.1" {
|
|
t.Fatalf("default gateway decoded incorrectly: %s", got)
|
|
}
|
|
}
|