A new step, internal/ownresync, after each shadow answer: for each own secret masked as another value, while the node applies the controller's compositions and runs the one its want names, it asks the vault's move-in with replace_running: true and the running value sealed to the call's key, once per value, and says what it asked and what the vault answered. The answer written to the node's have key carries values_differ. Pins the group's mesh-sdk head 1062b33 (go.sum is filled once that head is pushed).
103 lines
4.8 KiB
Go
103 lines
4.8 KiB
Go
package ownresync
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// Issue 1126, cause C4: after a shadow answer saying the vault holds another value of an own secret than runs, the node
|
|
// hands the vault what runs, in the verb's two steps, once per running value; and only while it applies the
|
|
// controller's compositions and runs the one its want names.
|
|
func TestTheNodeHandsTheVaultWhatRunsOfADriftedOwnSecret(t *testing.T) {
|
|
const running = "the-value-that-runs"
|
|
digest := "sha256:" + strings.Repeat("a", 64)
|
|
fp := "src2:" + strings.Repeat("b", 64)
|
|
sum := sha256.Sum256([]byte(running))
|
|
answers := map[string]delivery.CandidateAnswer{"g": {Head: "h", Verdict: delivery.CandidateDiffers,
|
|
Differs: []delivery.ModuleDiffers{{Module: "onlyoffice", Why: "the resource differs", Bodies: []delivery.ResourceBodies{
|
|
{ID: "needs-jwt", Composed: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`, Running: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`},
|
|
{ID: "needs-salt", Composed: `{"sealed":"<masked: another seal of the same value>"}`}}}}}}
|
|
for name, row := range map[string]struct {
|
|
composes string
|
|
runs string // the have's composition digest
|
|
refuse string // the vault's refusal
|
|
asks int // the vault's verb asked, both passes
|
|
says string
|
|
}{
|
|
"applies the controller's, runs its want": {delivery.ComposesController, digest, "", 2, "generation 1 replaced by 2"},
|
|
"composes its own": {delivery.ComposesOwn, digest, "", 0, "composes its own"},
|
|
"not decided yet": {"", digest, "", 0, ""},
|
|
"runs not yet what its want names": {delivery.ComposesController, "sha256:" + strings.Repeat("c", 64), "", 0, "not yet the composition"},
|
|
"the vault refuses": {delivery.ComposesController, digest, "switched off", 1, "switched off"},
|
|
} {
|
|
var asked []map[string]any
|
|
var said []string
|
|
want, _ := delivery.EncodeWant(delivery.Want{FP: fp, Composed: digest})
|
|
have, _ := delivery.EncodeHave(delivery.Have{FP: fp, Composed: row.runs, Phase: delivery.PhaseRunning, Health: delivery.HealthOK})
|
|
r := New(Env{Node: "novox", Composes: func() string { return row.composes },
|
|
Running: func() []delivery.Composed {
|
|
return []delivery.Composed{{Node: "novox", Module: "onlyoffice", FP: fp, Resources: []json.RawMessage{
|
|
json.RawMessage(`{"id":"needs-jwt","type":"file","path":"/etc/jwt","sealed":"box-of-the-running-value"}`)}}}
|
|
},
|
|
Get: func(_ context.Context, bucket, key string) ([]byte, error) {
|
|
switch {
|
|
case bucket == delivery.AssignerWantsBucket && key == "want.novox.onlyoffice":
|
|
return want, nil
|
|
case bucket == delivery.HaveBucket && key == "novox.onlyoffice":
|
|
return have, nil
|
|
}
|
|
return nil, nil
|
|
},
|
|
Unseal: func(sealed string) ([]byte, error) {
|
|
if sealed != "box-of-the-running-value" {
|
|
return nil, errors.New("not this machine's")
|
|
}
|
|
return []byte(running), nil
|
|
},
|
|
Seal: func(to string, value []byte) (string, error) { return to + "(" + string(value) + ")", nil },
|
|
Ask: func(_ context.Context, verb string, args map[string]any) (json.RawMessage, error) {
|
|
asked = append(asked, args)
|
|
if verb != "move-in" || args["replace_running"] != true {
|
|
t.Errorf("%s: asked %s with %v, not move-in with replace_running", name, verb, args)
|
|
}
|
|
if row.refuse != "" {
|
|
return nil, errors.New(row.refuse)
|
|
}
|
|
if args["call"] == nil {
|
|
if args["module"] != "onlyoffice" || args["name"] != "jwt" || args["node"] != nil {
|
|
t.Errorf("%s: the first step names %v", name, args)
|
|
}
|
|
return json.RawMessage(`{"call":"c1","seal_to":"key","secret":"own/novox/onlyoffice/jwt"}`), nil
|
|
}
|
|
if args["sealed"] != "key("+running+")" {
|
|
t.Errorf("%s: handed %v, not what runs sealed to the call's key", name, args["sealed"])
|
|
}
|
|
return json.RawMessage(`{"secret":"own/novox/onlyoffice/jwt","from_generation":1,"to_generation":2,` +
|
|
`"fingerprint":"sha256:` + hex.EncodeToString(sum[:]) + `"}`), nil
|
|
},
|
|
Say: func(s string) { said = append(said, s) }})
|
|
first := r.Take(context.Background(), answers)
|
|
again := r.Take(context.Background(), answers)
|
|
if len(asked) != row.asks {
|
|
t.Errorf("%s: the vault asked %d times, want %d: %v", name, len(asked), row.asks, asked)
|
|
}
|
|
if len(again) != 0 {
|
|
t.Errorf("%s: the same answer did or said something again: %+v", name, again)
|
|
}
|
|
all := strings.Join(said, "\n")
|
|
if row.says == "" && (len(first) != 0 || all != "") || row.says != "" && (len(first) != 1 || !strings.Contains(all, row.says)) {
|
|
t.Errorf("%s: did %+v and said %q, want %q", name, first, all, row.says)
|
|
}
|
|
if strings.Contains(all, running) {
|
|
t.Errorf("%s: the value is in what the node said", name)
|
|
}
|
|
}
|
|
}
|