219 lines
8.6 KiB
Go
219 lines
8.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
"github.com/novox/mesh-controller/internal/secrets"
|
|
)
|
|
|
|
// The `issued` verb runs `secret issued <provision>` and nothing else, and that line is the one `secret` line
|
|
// beside rotate and the desk's that a verb may run (novox/hq ADR 0305 §3, §10, issue 526).
|
|
func TestTheIssuedVerbRunsTheOneLineThatCarriesNoValue(t *testing.T) {
|
|
argv, err := argvFor("issued", map[string]any{"provision": "plex-api"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Join(argv, " ") != "secret issued plex-api" {
|
|
t.Fatalf("the verb composed %q", argv)
|
|
}
|
|
if _, err := argvFor("issued", map[string]any{}); err == nil {
|
|
t.Fatal("the verb ran with no provision")
|
|
}
|
|
if _, err := argvFor("issued", map[string]any{"provision": "plex-api", "secret": "a value"}); err == nil {
|
|
t.Fatal("the verb took a value beside the provision")
|
|
}
|
|
for _, line := range [][]string{
|
|
{"secret", "issued", "plex-api", "--from", "/tmp/x"},
|
|
{"secret", "issued", "--from"},
|
|
{"secret", "issued"},
|
|
{"secret", "accept", "ace", "kometa", "plex-api", "--provider", "ace"},
|
|
{"secret", "recover", "ace", "kometa", "plex-api"},
|
|
} {
|
|
if err := terminalOnly(line); err == nil {
|
|
t.Errorf("%q ran through a verb", line)
|
|
}
|
|
}
|
|
if err := terminalOnly([]string{"secret", "issued", "plex-api"}); err != nil {
|
|
t.Fatalf("the issued line was refused through its verb: %v", err)
|
|
}
|
|
found := false
|
|
for _, v := range catalogue.ControllerVerbs {
|
|
if v.Name == "issued" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("the controller's seat does not declare the issued verb")
|
|
}
|
|
}
|
|
|
|
// A provider's tool answering the value sealed to the call's key: what a provider does (ADR 0305 §2).
|
|
func issuedSealed(t *testing.T, value string) func(issuedProvider, map[string]any) (link.Answer, error) {
|
|
return func(_ issuedProvider, args map[string]any) (link.Answer, error) {
|
|
key, _ := args["seal_to"].(string)
|
|
if key == "" {
|
|
t.Fatal("the provider was asked without a key to seal to")
|
|
}
|
|
if _, carried := args["value"]; carried {
|
|
t.Fatal("the ask carried a value")
|
|
}
|
|
sealed, err := secrets.Seal(key, []byte(value))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, _ := json.Marshal(map[string]string{"sealed": sealed})
|
|
return link.Answer{Result: raw}, nil
|
|
}
|
|
}
|
|
|
|
type issuedRecorder struct {
|
|
kept []string
|
|
acts []link.HandAct
|
|
sent [][]string
|
|
again bool // the value is held by every holder already
|
|
}
|
|
|
|
func (r *issuedRecorder) taker(t *testing.T, ask func(issuedProvider, map[string]any) (link.Answer, error)) issuedTaker {
|
|
return issuedTaker{
|
|
providers: func(provision string) ([]issuedProvider, bool, error) {
|
|
return []issuedProvider{{Node: "ace", Module: "plex", Tool: "plex_issued_credential"}}, true, nil
|
|
},
|
|
ask: ask,
|
|
keep: func(provision, provider, value string) (inventory.IssuedKept, error) {
|
|
r.kept = append(r.kept, value)
|
|
holders := []inventory.Holder{{Provision: provision, Consumer: "ace", ConsumerModule: "kometa", Provider: provider},
|
|
{Provision: provision, Consumer: "ace", ConsumerModule: "tautulli", Provider: provider}}
|
|
if r.again {
|
|
return inventory.IssuedKept{Holders: holders}, nil
|
|
}
|
|
return inventory.IssuedKept{Changed: true, Holders: holders, Sealed: holders, Machines: []string{"ace"}}, nil
|
|
},
|
|
record: func(act link.HandAct) error { r.acts = append(r.acts, act); return nil },
|
|
send: func(machines []string) error { r.sent = append(r.sent, machines); return nil },
|
|
}
|
|
}
|
|
|
|
// The value the provider's tool sealed to the call is kept, recorded and sent, and the answer never says it.
|
|
func TestAnIssuedValueIsTakenSealedFromTheProviderAndSent(t *testing.T) {
|
|
var r issuedRecorder
|
|
words, err := r.taker(t, issuedSealed(t, "the-servers-own-token")).take("plex-api")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(r.kept) != 1 || r.kept[0] != "the-servers-own-token" {
|
|
t.Fatalf("the provider's value was not kept: %d kept", len(r.kept))
|
|
}
|
|
if len(r.sent) != 1 || strings.Join(r.sent[0], ",") != "ace" {
|
|
t.Fatalf("the holders' machines were not sent: %v", r.sent)
|
|
}
|
|
if len(r.acts) != 1 || r.acts[0].Verb != handActIssued {
|
|
t.Fatalf("the change was not recorded in the hand-act log: %+v", r.acts)
|
|
}
|
|
for _, said := range []string{words, r.acts[0].Why, strings.Join(r.acts[0].Args, " ")} {
|
|
if strings.Contains(said, "the-servers-own-token") || strings.Contains(said, inventory.IssuedStamp("the-servers-own-token")) {
|
|
t.Fatalf("the value or its fingerprint was said: %q", said)
|
|
}
|
|
}
|
|
if !strings.Contains(words, "kometa on ace") || !strings.Contains(words, "tautulli on ace") {
|
|
t.Fatalf("the answer does not name the holders: %q", words)
|
|
}
|
|
}
|
|
|
|
// The same value again writes nothing, records nothing and sends nobody (ADR 0305 §5).
|
|
func TestAnIssuedValueEveryHolderHoldsSendsNobody(t *testing.T) {
|
|
r := issuedRecorder{again: true}
|
|
words, err := r.taker(t, issuedSealed(t, "the-servers-own-token")).take("plex-api")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(r.sent) != 0 || len(r.acts) != 0 {
|
|
t.Fatalf("an unchanged value was sent or recorded: %v %v", r.sent, r.acts)
|
|
}
|
|
if !strings.Contains(words, "nothing was sent") {
|
|
t.Fatalf("the answer does not say nothing was sent: %q", words)
|
|
}
|
|
}
|
|
|
|
// A value not sealed to the call's key is not taken, nor is a refusal, nor an empty answer (ADR 0305 §2).
|
|
func TestOnlyAValueSealedToTheCallIsTaken(t *testing.T) {
|
|
other, _, _ := secrets.Keypair()
|
|
elsewhere, _ := secrets.Seal(other, []byte("somebody's value"))
|
|
raw, _ := json.Marshal(map[string]string{"sealed": elsewhere})
|
|
plain, _ := json.Marshal(map[string]string{"value": "the-servers-own-token"})
|
|
for name, ask := range map[string]func(issuedProvider, map[string]any) (link.Answer, error){
|
|
"sealed to another key": func(issuedProvider, map[string]any) (link.Answer, error) { return link.Answer{Result: raw}, nil },
|
|
"answered in the clear": func(issuedProvider, map[string]any) (link.Answer, error) { return link.Answer{Result: plain}, nil },
|
|
"refused": func(issuedProvider, map[string]any) (link.Answer, error) {
|
|
return link.Answer{Error: "the controller alone"}, nil
|
|
},
|
|
"not reached": func(issuedProvider, map[string]any) (link.Answer, error) {
|
|
return link.Answer{}, errors.New("503 no responders")
|
|
},
|
|
"sealed empty": issuedSealed(t, " "),
|
|
} {
|
|
var r issuedRecorder
|
|
if _, err := r.taker(t, ask).take("plex-api"); err == nil {
|
|
t.Errorf("%s: taken", name)
|
|
}
|
|
if len(r.kept) != 0 || len(r.sent) != 0 {
|
|
t.Errorf("%s: something was kept or sent", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A provision no offer says its software issues is refused before anything is asked.
|
|
func TestAProvisionNoOfferSaysIsIssuedIsRefused(t *testing.T) {
|
|
asked := false
|
|
taker := issuedTaker{
|
|
providers: func(string) ([]issuedProvider, bool, error) { return nil, false, nil },
|
|
ask: func(issuedProvider, map[string]any) (link.Answer, error) {
|
|
asked = true
|
|
return link.Answer{}, nil
|
|
},
|
|
}
|
|
if _, err := taker.take("postgres-database"); err == nil || !strings.Contains(err.Error(), "ADR 0305") {
|
|
t.Fatalf("expected a refusal naming the record, got %v", err)
|
|
}
|
|
if asked {
|
|
t.Fatal("a provider was asked for a provision no offer says its software issues")
|
|
}
|
|
}
|
|
|
|
// Which assignments are asked: the modules on the providing machine whose offer says so, with their tool.
|
|
func TestTheProvidersAskedAreTheOffersThatSaySo(t *testing.T) {
|
|
shelf := map[string]catalogue.Manifest{
|
|
"plex": {Module: "plex", Provides: []catalogue.Offer{{Name: "plex-api", Credential: &catalogue.OfferCredential{
|
|
IssuedBy: catalogue.IssuedBySoftware, Tool: "plex_issued_credential"}}}},
|
|
"jellyfin": {Module: "jellyfin", Provides: []catalogue.Offer{{Name: "plex-api"}}},
|
|
}
|
|
got := issuedOn(shelf, "ace", []string{"jellyfin", "plex", "kometa"}, "plex-api")
|
|
if len(got) != 1 || got[0] != (issuedProvider{Node: "ace", Module: "plex", Tool: "plex_issued_credential"}) {
|
|
t.Fatalf("the providers asked: %+v", got)
|
|
}
|
|
if err := issuedRefusesRotation(shelf, "plex-api"); err == nil || !strings.Contains(err.Error(), "secret issued plex-api") {
|
|
t.Fatalf("rotate of an issued provision was not refused with the way to take it: %v", err)
|
|
}
|
|
if err := issuedRefusesRotation(shelf, "postgres-database"); err != nil {
|
|
t.Fatalf("rotate of an ordinary provision was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// A change of an issued value is the mesh working as decided, never a repair (ADR 0305 §8).
|
|
func TestAnIssuedValueIsADecisionInTheHandActLog(t *testing.T) {
|
|
for _, v := range handActVerbs {
|
|
if v.Verb == handActIssued {
|
|
if v.Decision == "" {
|
|
t.Fatal("secret issued counts as a repair")
|
|
}
|
|
return
|
|
}
|
|
}
|
|
t.Fatal("secret issued is not in the hand-act table")
|
|
}
|