package main import ( "bytes" "crypto/ed25519" "crypto/rand" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "os" "path/filepath" "sort" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/compose" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/overlay" ) // aMesh is the Input of mesh-controller's aMesh, as mesh-sdk's compose tests make it (compose/fixture_test.go): two // machines on the private network, the module the mesh provides for itself assigned to both. The manifest is the one // the controller provides (overlay.Manifest), read back as the catalogue reads it. func aMesh(t *testing.T) compose.Input { t.Helper() var m catalogue.Manifest b, _ := json.Marshal(overlay.Manifest()) if err := json.Unmarshal(b, &m); err != nil { t.Fatal(err) } in := compose.Input{Catalogue: map[string]catalogue.Manifest{overlay.Name: m}} for i, name := range []string{"anchor", "laptop"} { pub, _, _ := ed25519.GenerateKey(rand.Reader) in.Nodes = append(in.Nodes, compose.Node{Name: name, Place: compose.Place{Key: base64.StdEncoding.EncodeToString(pub), Endpoint: name + ".example:51820", Site: "here", Hub: name == "anchor", Address: []string{"10.77.0.1", "10.77.0.2"}[i]}, Assigned: []string{overlay.Name}, Capabilities: map[string]bool{"container-runtime": true, "wireguard": true, "systemd": true}}) } return in } func writeKey(t *testing.T, dir string) ed25519.PublicKey { t.Helper() pub, priv, _ := ed25519.GenerateKey(rand.Reader) der, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "operator.key"), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der}), 0o600); err != nil { t.Fatal(err) } return pub } // 1943/AC5: mesh-compose, given the kept inputs, a node, a module and its fingerprint, prints that module's // composition for that node signed by the operator's key and its digest: its resources are the controller's for that // module on that node (compose.Compose), and it verifies against the operator's key. Without --write it writes nothing. func TestMeshComposeGivesTheControllersResourcesSignedByTheOperator(t *testing.T) { dir := t.TempDir() in := aMesh(t) raw, err := json.Marshal(in) if err != nil { t.Fatal(err) } input := filepath.Join(dir, "input.json") if err := os.WriteFile(input, raw, 0o600); err != nil { t.Fatal(err) } operator := writeKey(t, dir) fp := delivery.SourcePrefix2 + strings.Repeat("a", 64) var out bytes.Buffer if err := run([]string{"-input", input, "-node", "laptop", "-module", overlay.Name, "-fp", fp, "-key", filepath.Join(dir, "operator.key")}, &out); err != nil { t.Fatal(err) } var printed struct { Key string `json:"key"` Bucket string `json:"bucket"` Digest string `json:"digest"` Composition json.RawMessage `json:"composition"` Written bool `json:"written"` } if err := json.Unmarshal(out.Bytes(), &printed); err != nil { t.Fatalf("%s: %v", out.String(), err) } c, err := delivery.DecodeComposed(printed.Composition) if err != nil { t.Fatal(err) } if digest, err := delivery.VerifyComposed(c, []ed25519.PublicKey{operator}); err != nil || digest != printed.Digest { t.Errorf("the composition verifies %q, %v against the operator's key; printed %s", digest, err, printed.Digest) } wantKey, _ := delivery.ComposedKey("laptop", overlay.Name, fp) if printed.Key != wantKey || printed.Bucket != "composed-laptop" || printed.Written { t.Errorf("printed key %s in %s, written %v", printed.Key, printed.Bucket, printed.Written) } d, _, err := compose.Compose(in, "laptop") if err != nil { t.Fatal(err) } var want, got []string for _, r := range d.Resources { want = append(want, r["id"].(string)) } for _, r := range c.Resources { var head struct{ ID string } _ = json.Unmarshal(r, &head) got = append(got, head.ID) } sort.Strings(want) sort.Strings(got) if len(got) == 0 || strings.Join(got, " ") != strings.Join(want, " ") { t.Errorf("mesh-compose gives %v, the controller's composition %v", got, want) } } // 1943/AC5: mesh-compose refuses what it cannot compose, naming why: no key, a fingerprint not of the new form, a // module the node does not run, a node the mesh does not have. func TestMeshComposeRefuses(t *testing.T) { dir := t.TempDir() raw, _ := json.Marshal(aMesh(t)) input := filepath.Join(dir, "input.json") _ = os.WriteFile(input, raw, 0o600) writeKey(t, dir) key := filepath.Join(dir, "operator.key") fp := delivery.SourcePrefix2 + strings.Repeat("a", 64) for _, c := range []struct { args []string why string }{ {[]string{"-input", input, "-node", "laptop", "-module", overlay.Name, "-fp", fp}, "-key"}, {[]string{"-input", input, "-node", "laptop", "-module", overlay.Name, "-fp", "src2:1", "-key", key}, "fingerprint"}, {[]string{"-input", input, "-node", "laptop", "-module", "mesh-mail", "-fp", fp, "-key", key}, "does not run mesh-mail"}, {[]string{"-input", input, "-node", "nowhere", "-module", overlay.Name, "-fp", fp, "-key", key}, "nowhere"}, {[]string{"-input", input, "-node", "laptop", "-module", overlay.Name, "-fp", fp, "-key", key, "-write"}, "-creds"}, } { var out bytes.Buffer if err := run(c.args, &out); err == nil || !strings.Contains(err.Error(), c.why) { t.Errorf("mesh-compose %v = %v, want a refusal saying %q", c.args, err, c.why) } } }