diff --git a/module.json b/module.json index ffa13e3..d92aa6c 100644 --- a/module.json +++ b/module.json @@ -14,6 +14,13 @@ } ] }, + "config": { + "operator-signers": { + "type": "string", + "description": "The operator's public keys, ed25519 in base64, one a line: the node applies a composition made by hand only when one of these signed it (ADR 0539 clause 3). Public keys, no secret. Empty trusts no operator key.", + "default": "" + } + }, "resources": [ { "id": "launcher", @@ -27,6 +34,13 @@ "type": "archive", "artifact": "host-arch", "path": "/usr/lib/nox-mesh-host/versions/${version}" + }, + { + "id": "operator-keys", + "type": "file", + "path": "/var/lib/mesh-host/operator-keys", + "mode": "0644", + "content": "${config:operator-signers}\n" } ] } diff --git a/packaging/operator_keys_test.go b/packaging/operator_keys_test.go new file mode 100644 index 0000000..723cd2b --- /dev/null +++ b/packaging/operator_keys_test.go @@ -0,0 +1,54 @@ +package packaging_test + +import ( + "encoding/json" + "os" + "strings" + "testing" + + "git.novox.be/novox/mesh-sdk/go/catalogue" +) + +// The operator's public keys reach every node as a file beside the state file, fed by a setting of the node-engine +// (novox/hq ADR 0539 clauses 3 and 11): the node agent reads it, and a composition made by hand is applied only when one +// of these keys signed it. The setting may be empty, and then no operator key is trusted. +func TestTheManifestDeclaresTheOperatorKeysFileFedByASetting(t *testing.T) { + raw, err := os.ReadFile("../module.json") + if err != nil { + t.Fatal(err) + } + if _, err := catalogue.ParseManifest(raw); err != nil { + t.Fatalf("the manifest no longer parses: %v", err) + } + var m struct { + Config map[string]struct { + Type string `json:"type"` + Default any `json:"default"` + } `json:"config"` + Resources []struct { + ID string `json:"id"` + Type string `json:"type"` + Path string `json:"path"` + Mode string `json:"mode"` + Content string `json:"content"` + } `json:"resources"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + setting, ok := m.Config["operator-signers"] + if !ok || setting.Type != "string" || setting.Default != "" { + t.Fatalf("the setting operator-signers is %+v: a string that may be empty", setting) + } + for _, r := range m.Resources { + if r.ID != "operator-keys" { + continue + } + if r.Type != "file" || r.Path != "/var/lib/mesh-host/operator-keys" || r.Mode != "0644" || + !strings.Contains(r.Content, "${config:operator-signers}") { + t.Fatalf("the file is %+v: it is declared beside the state file, 0644, its content the setting", r) + } + return + } + t.Fatal("module.json declares no operator-keys file") +}