Every check the mesh had was right about the world it was given and none was given the mesh's: a real machine's name made an identity too long (263), the node-engine refused what the catalogue check passed (236). The controller now composes what a check needs - every machine under a pseudonym of its name's length, its roles, system, builds, capabilities, assignments, pins, settings and how its declaration composes; every seat, module and source; the bus, store and node-engine versions it runs - with no secret, no address and no name, and keeps it in the artifact store as facts:latest when it moved, or daily. The replaced snapshot's manifest is let go of, so the nightly collector takes it. S14 raises facts-stale past two days.
160 lines
5.2 KiB
Go
160 lines
5.2 KiB
Go
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)
|
|
}
|
|
}
|