package facts import ( "fmt" "net" "regexp" "strings" "testing" "time" ) // A pseudonym keeps what a limit meets — the length, and letters where letters were — and nothing else. func TestAPseudonymKeepsTheLengthAndShapeAndIsStable(t *testing.T) { for _, name := range []string{"ace", "g14", "novox", "shanks", "home-server", "a"} { p := Pseudonym("machine", name) if len(p) != len(name) { t.Errorf("%s became %s: %d characters for %d", name, p, len(p), len(name)) } if p == name { t.Errorf("%s was kept as itself", name) } if p != Pseudonym("machine", name) { t.Errorf("%s is not stable", name) } for i := range name { isLetter := func(c byte) bool { return c >= 'a' && c <= 'z' } isDigit := func(c byte) bool { return c >= '0' && c <= '9' } if isLetter(name[i]) != isLetter(p[i]) || isDigit(name[i]) != isDigit(p[i]) { t.Errorf("%s became %s: the shape moved at %d", name, p, i) } } } if Pseudonym("machine", "ace") == Pseudonym("site", "ace") { t.Error("a site and a machine of one name share a pseudonym, so a snapshot says they are one thing") } } // Nothing of the installation survives the scrubber: names, domains, accounts, addresses, mail, secrets. func TestTheScrubberLeavesNothingOfTheInstallation(t *testing.T) { s := NewScrubber() machine := s.Machine("homeserver") s.Account("jochens") domain := s.Domain("zurag.be") if len(domain) != len("zurag.be") || !strings.HasSuffix(domain, ".be") || domain == "zurag.be" { t.Errorf("the domain became %q", domain) } in := map[string]any{ "hub": "homeserver", "listen": "10.42.0.7:51820", "upstream": []any{"192.168.1.135", "fd00::1"}, "site": "https://grafana.zurag.be/login", "admin": "jschoubben@gmail.com", "home": "/home/jochens/.ssh", "api_key": "sk-live-abcdef", "nested": map[string]any{"password": "hunter2", "port": float64(5432)}, "opaque": "a8F3kQ9zL2mX7vB4nC6dE1rT5yU0iO8pA3sD", "dsn": "postgres://app:s3cr3tpass@db.internal:5432/app", "pem": "-----BEGIN PRIVATE KEY-----\nMIIB", "plain": "a-long-plain-module-directory-name", "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "version": "2.11.17", "homeserver": true, } out := s.Values(in) flat := flatten(out) for _, leaked := range []string{"homeserver\"", "10.42.0.7", "192.168.1.135", "fd00::1", "zurag", "jschoubben", "gmail", "jochens", "sk-live", "hunter2", "a8F3kQ9zL2mX7vB4nC6dE1rT5yU0iO8pA3sD", "s3cr3tpass", "MIIB"} { if strings.Contains(flat, leaked) { t.Errorf("%q survived the scrubber:\n%s", leaked, flat) } } if out["hub"] != machine { t.Errorf("a machine named in a setting became %v, not its pseudonym %s", out["hub"], machine) } for _, kept := range []string{"a-long-plain-module-directory-name", "2.11.17", "sha256:44136fa3", "5432"} { if !strings.Contains(flat, kept) { t.Errorf("%q was scrubbed, and it is not the installation's:\n%s", kept, flat) } } // Every address left is a documentation address. for _, a := range regexp.MustCompile(`[0-9a-f:.]{7,}`).FindAllString(flat, -1) { ip := net.ParseIP(strings.Trim(a, ".:")) if ip == nil { continue } doc := false for _, cidr := range []string{"192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24", "2001:db8::/32"} { _, n, _ := net.ParseCIDR(cidr) doc = doc || n.Contains(ip) } if !doc { t.Errorf("%s is not a documentation address", a) } } // The same address is always the same stand-in. if s.Text("10.42.0.7") != s.Text("at 10.42.0.7")[3:] { t.Error("one address became two stand-ins") } } func flatten(v any) string { var b strings.Builder var walk func(any) walk = func(v any) { switch t := v.(type) { case map[string]any: for k, e := range t { b.WriteString(k + "\"=") walk(e) b.WriteString("\n") } case []any: for _, e := range t { walk(e) b.WriteString(",") } case string: b.WriteString(t + "\"") default: b.WriteString(fmt.Sprint(t)) } } walk(v) return b.String() } // Two snapshots of one mesh, taken apart, are one content. func TestASnapshotOfAnUnchangedMeshIsTheSameContentAnotherDay(t *testing.T) { f := Facts{Format: Format, Taken: time.Now(), Machines: []Machine{{Name: "b"}, {Name: "a"}}} g := f g.Taken = f.Taken.Add(24 * time.Hour) g.Machines = []Machine{{Name: "a"}, {Name: "b"}} a, err := f.Content() if err != nil { t.Fatal(err) } b, _ := g.Content() if a != b { t.Error("the same mesh a day later reads as a change") } g.Machines = append(g.Machines, Machine{Name: "c"}) if c, _ := g.Content(); c == a { t.Error("a machine added reads as no change") } } // A reader refuses a snapshot it cannot read whole. func TestANewerSnapshotIsRefusedNotHalfRead(t *testing.T) { if _, err := Decode([]byte(`{"facts": 99}`)); err == nil || !strings.Contains(err.Error(), "newer controller") { t.Errorf("a newer format read as %v", err) } if _, err := Decode([]byte(`{"machines": []}`)); err == nil { t.Error("a document with no format read as a snapshot") } f, err := Decode([]byte(`{"facts": 1, "machines": [{"name": "abc", "length": 3}, {"name": "defgh", "length": 5}]}`)) if err != nil || f.Longest() != 5 { t.Errorf("read %+v, %v", f, err) } }