Prove the vault's secret-ask test and the issuing provider's test with a stand-in provider (issue 675)
mesh/merge-gate head eb958f668ecd merged into trunk 1a2c917abfdb — pass: [release feat/57-config-and-provisioning, judged by its controller fcdecb436117] t…
mesh/repo-check head eb958f668ecd merged into trunk 1a2c917abfdb — fail: [release feat/57-config-and-provisioning, judged by its controller fcdecb436117] i…
mesh/delivery-group group pkg/57-v2-vault-verbs rejected: a member's own check failed
mesh/delivery stopped: the pull request closed unmerged
mesh/merge-gate head eb958f668ecd merged into trunk 1a2c917abfdb — pass: [release feat/57-config-and-provisioning, judged by its controller fcdecb436117] t…
mesh/repo-check head eb958f668ecd merged into trunk 1a2c917abfdb — fail: [release feat/57-config-and-provisioning, judged by its controller fcdecb436117] i…
mesh/delivery-group group pkg/57-v2-vault-verbs rejected: a member's own check failed
mesh/delivery stopped: the pull request closed unmerged
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,495 @@
|
||||
package main
|
||||
|
||||
// ADR 0349's tests for what the vault takes in (design 57's V2, issue 675): the vault's `secret-ask` test, the
|
||||
// issuing provider's test with a stand-in provider (plex's own tool moves with the media catalogue, after
|
||||
// release 57), an accepted value opened in the vault alone and never remade, and rotate.
|
||||
//
|
||||
// In each, "the controller" is a test function that holds no private key: it only carries what the desk or the
|
||||
// provider sealed to the key the vault made for the call, as the controller does on the bus.
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
|
||||
// sealToCall seals value to a call's key as the desk's prompt and a provider's tool do: the NaCl anonymous box.
|
||||
func sealToCall(t *testing.T, sealTo, value string) string {
|
||||
t.Helper()
|
||||
raw, err := base64.StdEncoding.DecodeString(sealTo)
|
||||
if err != nil || len(raw) != 32 {
|
||||
t.Fatalf("seal_to %q is not a key", sealTo)
|
||||
}
|
||||
var key [32]byte
|
||||
copy(key[:], raw)
|
||||
sealed, err := box.SealAnonymous(nil, []byte(value), &key, rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(sealed)
|
||||
}
|
||||
|
||||
// handedTo opens what `get` hands an allowed asker: the value the vault holds.
|
||||
func handedTo(t *testing.T, v testVault, asker, name string) string {
|
||||
t.Helper()
|
||||
key, _ := seal.GenerateKeyPair()
|
||||
a, err := v.Get(asker, name, key.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
value, err := seal.Open(a.Sealed, key.PrivateKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// everything is all the vault said and wrote during a test, as text.
|
||||
func everything(t *testing.T, v testVault, more ...any) string {
|
||||
t.Helper()
|
||||
var b strings.Builder
|
||||
for _, said := range v.bus.raw {
|
||||
b.WriteString(said)
|
||||
}
|
||||
for _, content := range filesUnder(t, v.dir) {
|
||||
b.WriteString(content)
|
||||
}
|
||||
for _, m := range more {
|
||||
raw, _ := json.Marshal(m)
|
||||
b.Write(raw)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
const typedAtTheDesk = "123456789:AAEhBP0av28P4XFQnIuR-o-7Xnz1kkUzW3g"
|
||||
|
||||
func telegramAsk(by string) AskOf {
|
||||
return AskOf{Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: by}
|
||||
}
|
||||
|
||||
// ---- the vault's secret-ask test (ADR 0349 "How it is checked") ---------------------------------------------
|
||||
|
||||
// An ask is recorded in the vault's store before the prompt opens; its prompt names the asker as the bus named
|
||||
// it; the typed value opens only in the vault; the controller carries ciphertext only.
|
||||
func TestAnAskIsRecordedBeforeThePromptAndTheTypedValueOpensOnlyInTheVault(t *testing.T) {
|
||||
v := newVault(t, AllowList{"anchor.node-provisioner": {"own/anchor/telegram/telegram-token"}})
|
||||
opened, err := v.AskOpen(ControllerCaller, telegramAsk("g14/claude-code, via the console"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Recorded before any prompt opens: the record holds the ask now, with no end.
|
||||
asks, _ := v.Store.Asks(10)
|
||||
if len(asks) != 1 || asks[0].Event != AskOpened || asks[0].ID != opened.Call || asks[0].AskedBy != "g14/claude-code" ||
|
||||
asks[0].Caller != ControllerCaller || asks[0].Desk != "laptop" || asks[0].Secret != "own/anchor/telegram/telegram-token" {
|
||||
t.Fatalf("the record before the prompt: %+v", asks)
|
||||
}
|
||||
// The prompt names the asker as the bus named it, cut to a name: what the vault answers is what it recorded.
|
||||
if opened.AskedBy != "g14/claude-code" {
|
||||
t.Errorf("the prompt would name %q", opened.AskedBy)
|
||||
}
|
||||
// The desk seals what is typed to the call's key; the controller passes it on.
|
||||
changed, err := v.AskEnd(ControllerCaller, opened.Call, sealToCall(t, opened.SealTo, typedAtTheDesk+"\n"), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if changed.Generation != 1 || changed.By != "g14/claude-code" {
|
||||
t.Errorf("kept as %+v", changed)
|
||||
}
|
||||
if got := handedTo(t, v, "anchor.node-provisioner", "own/anchor/telegram/telegram-token"); got != typedAtTheDesk {
|
||||
t.Errorf("the vault holds %q, not what was typed with its line ending taken off", got)
|
||||
}
|
||||
rec, _, _ := v.Store.Read("own/anchor/telegram/telegram-token")
|
||||
if rec.Origin != OriginTyped || rec.ChangedBy != "g14/claude-code" || rec.ChangedVia != ControllerCaller {
|
||||
t.Errorf("the record says origin %q, by %q, via %q", rec.Origin, rec.ChangedBy, rec.ChangedVia)
|
||||
}
|
||||
asks, _ = v.Store.Asks(10)
|
||||
if len(asks) != 2 || asks[0].Event != AskEnded || asks[0].Outcome != "given" || asks[0].ID != opened.Call {
|
||||
t.Errorf("how it ended: %+v", asks)
|
||||
}
|
||||
if strings.Contains(everything(t, v, opened, changed, asks), typedAtTheDesk) {
|
||||
t.Fatal("the typed value is in something the vault said, answered or wrote in the clear")
|
||||
}
|
||||
}
|
||||
|
||||
// One open ask per secret, and three an hour (ADR 0277 §24), read from the vault's own record.
|
||||
func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
first, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := v.AskOpen(ControllerCaller, telegramAsk("ace.claude-code")); err == nil ||
|
||||
!strings.Contains(err.Error(), "still open (asked by g14.claude-code)") {
|
||||
t.Fatalf("a second ask while one is open: %v", err)
|
||||
}
|
||||
// Another secret is not held back by it.
|
||||
other := telegramAsk("ace.claude-code")
|
||||
other.Name = "webhook-secret"
|
||||
if _, err := v.AskOpen(ControllerCaller, other); err != nil {
|
||||
t.Fatalf("another secret's ask was held back: %v", err)
|
||||
}
|
||||
if _, err := v.AskEnd(ControllerCaller, first.Call, "", "dismissed"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
o, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
if err != nil {
|
||||
t.Fatalf("ask %d of the hour: %v", i+2, err)
|
||||
}
|
||||
if _, err := v.AskEnd(ControllerCaller, o.Call, "", "timed-out"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code")); err == nil ||
|
||||
!strings.Contains(err.Error(), "3 times in the last hour") {
|
||||
t.Fatalf("a fourth ask in the hour: %v", err)
|
||||
}
|
||||
asks, _ := v.Store.Asks(50)
|
||||
outcomes := map[string]int{}
|
||||
for _, a := range asks {
|
||||
outcomes[a.Outcome]++
|
||||
}
|
||||
if outcomes["dismissed"] != 1 || outcomes["timed-out"] != 2 {
|
||||
t.Errorf("the record's outcomes: %v", outcomes)
|
||||
}
|
||||
// An ask left open counts as open for SecretAskOpenFor only: a prompt that died does not hold the secret.
|
||||
w := newVault(t, nil)
|
||||
clock := time.Date(2026, 10, 14, 9, 0, 0, 0, time.UTC)
|
||||
w.Now = func() time.Time { return clock }
|
||||
if _, err := w.AskOpen(ControllerCaller, telegramAsk("g14.claude-code")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clock = clock.Add(SecretAskOpenFor + time.Second)
|
||||
if _, err := w.AskOpen(ControllerCaller, telegramAsk("g14.claude-code")); err != nil {
|
||||
t.Errorf("an ask left open past its time still held the secret: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Only the controller reaches a desk's prompt, so only it opens and ends an ask; a value not sealed to the
|
||||
// call's key, or a call that waited too long, keeps nothing.
|
||||
func TestAnAskIsTakenFromTheControllerAloneAndOnlySealedToItsCall(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
for _, caller := range []string{"g14.node-tools", "unproven", "", "person.jochen"} {
|
||||
if _, err := v.AskOpen(caller, telegramAsk("x")); err == nil {
|
||||
t.Errorf("%q opened an ask", caller)
|
||||
}
|
||||
}
|
||||
if asks, _ := v.Store.Asks(10); len(asks) != 0 {
|
||||
t.Fatalf("a refused caller filled the record: %+v", asks)
|
||||
}
|
||||
opened, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := v.AskEnd("g14.node-tools", opened.Call, sealToCall(t, opened.SealTo, "x"), ""); err == nil {
|
||||
t.Error("another caller ended the ask")
|
||||
}
|
||||
other, _, _ := box.GenerateKey(rand.Reader)
|
||||
if _, err := v.AskEnd(ControllerCaller, opened.Call, sealToCall(t, base64.StdEncoding.EncodeToString(other[:]), "x"), ""); err == nil ||
|
||||
!strings.Contains(err.Error(), "not sealed to this call") {
|
||||
t.Errorf("a value sealed to another key: %v", err)
|
||||
}
|
||||
if _, held, _ := v.Store.Read("own/anchor/telegram/telegram-token"); held {
|
||||
t.Error("a value not sealed to the call was kept")
|
||||
}
|
||||
// The call's key is forgotten once used: the same call cannot be answered twice.
|
||||
if _, err := v.AskEnd(ControllerCaller, opened.Call, sealToCall(t, opened.SealTo, "x"), ""); err == nil {
|
||||
t.Error("a call was answered twice")
|
||||
}
|
||||
asks, _ := v.Store.Asks(1)
|
||||
if asks[0].Outcome != "failed" {
|
||||
t.Errorf("the ask that failed ended %+v", asks[0])
|
||||
}
|
||||
// A call that waited longer than its time is closed.
|
||||
clock := time.Date(2026, 10, 14, 9, 0, 0, 0, time.UTC)
|
||||
v.Now = func() time.Time { return clock }
|
||||
late, err := v.IssuedOpen(ControllerCaller, "plex-api", "anchor", "plex")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clock = clock.Add(CallOpenFor + time.Second)
|
||||
if _, err := v.IssuedTake(ControllerCaller, late.Call, sealToCall(t, late.SealTo, "x")); err == nil {
|
||||
t.Error("a call that waited too long was taken")
|
||||
}
|
||||
// An empty answer gives nothing and says so.
|
||||
clock = clock.Add(time.Hour)
|
||||
empty, _ := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
|
||||
if _, err := v.AskEnd(ControllerCaller, empty.Call, sealToCall(t, empty.SealTo, " \n"), ""); err == nil {
|
||||
t.Error("an empty answer was kept")
|
||||
}
|
||||
if asks, _ := v.Store.Asks(1); asks[0].Outcome != "empty" {
|
||||
t.Errorf("an empty answer ended %+v", asks[0])
|
||||
}
|
||||
}
|
||||
|
||||
// ---- the issuing provider's test, with a stand-in provider -------------------------------------------------
|
||||
|
||||
// standInProvider is a provider's tool for an `issued-by: software` offer, as plex's must answer (ADR 0305 §2
|
||||
// with the vault in the controller's place): it refuses every caller but the controller — the one caller the
|
||||
// bus lets ask it, carrying the vault's call — and answers only the software's value sealed to the call's key.
|
||||
func standInProvider(software *string) func(caller string, args map[string]any) (map[string]any, error) {
|
||||
return func(caller string, args map[string]any) (map[string]any, error) {
|
||||
if caller != ControllerCaller {
|
||||
return nil, errors.New("this tool answers the controller alone")
|
||||
}
|
||||
key, _ := args["seal_to"].(string)
|
||||
raw, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil || len(raw) != 32 {
|
||||
return nil, errors.New("seal_to is not a sealing key")
|
||||
}
|
||||
var to [32]byte
|
||||
copy(to[:], raw)
|
||||
sealed, err := box.SealAnonymous(nil, []byte(*software), &to, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"sealed": base64.StdEncoding.EncodeToString(sealed)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// issue is the controller's `secret issued` for one provider, carried as the controller carries it: open the
|
||||
// vault's call, ask the provider's tool with the call's key, pass on what it sealed.
|
||||
func issue(t *testing.T, v testVault, provider func(string, map[string]any) (map[string]any, error)) (Changed, error) {
|
||||
t.Helper()
|
||||
opened, err := v.IssuedOpen(ControllerCaller, "plex-api", "anchor", "plex")
|
||||
if err != nil {
|
||||
return Changed{}, err
|
||||
}
|
||||
answer, err := provider(ControllerCaller, map[string]any{"provision": "plex-api", "seal_to": opened.SealTo})
|
||||
if err != nil {
|
||||
return Changed{}, err
|
||||
}
|
||||
return v.IssuedTake(ControllerCaller, opened.Call, answer["sealed"].(string))
|
||||
}
|
||||
|
||||
func TestAnIssuingProviderAnswersOnlyTheControllersCallSealedToTheVault(t *testing.T) {
|
||||
v := newVault(t, AllowList{"anchor.node-provisioner": {"provision/anchor/plex-api"}})
|
||||
software := "plex-token-from-the-vendor"
|
||||
provider := standInProvider(&software)
|
||||
|
||||
// The provider's tool refuses every caller but the controller, whatever it asks.
|
||||
for _, caller := range []string{"anchor.node-tools", "g14.claude-code", "unproven", ""} {
|
||||
if _, err := provider(caller, map[string]any{"provision": "plex-api", "seal_to": "x"}); err == nil {
|
||||
t.Errorf("the provider's tool answered %q", caller)
|
||||
}
|
||||
}
|
||||
// The vault takes the steps from the controller alone.
|
||||
if _, err := v.IssuedOpen("anchor.node-tools", "plex-api", "anchor", "plex"); err == nil {
|
||||
t.Error("the vault opened an issued call for another caller")
|
||||
}
|
||||
|
||||
changed, err := issue(t, v, provider)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if changed.Name != "provision/anchor/plex-api" || changed.Generation != 1 || changed.By != "plex on anchor" {
|
||||
t.Errorf("kept as %+v", changed)
|
||||
}
|
||||
if got := handedTo(t, v, "anchor.node-provisioner", "provision/anchor/plex-api"); got != software {
|
||||
t.Errorf("the vault holds %q", got)
|
||||
}
|
||||
rec, _, _ := v.Store.Read("provision/anchor/plex-api")
|
||||
if rec.Origin != OriginIssued || rec.ChangedVia != ControllerCaller {
|
||||
t.Errorf("the record: origin %q via %q", rec.Origin, rec.ChangedVia)
|
||||
}
|
||||
|
||||
// Nothing rewritten for a value every holder has (ADR 0305 §5): no new generation, no write, nothing said.
|
||||
path := filepath.Join(v.dir, "store", "secrets", "provision%2Fanchor%2Fplex-api.json")
|
||||
before, _ := os.ReadFile(path)
|
||||
said := len(v.bus.raw)
|
||||
again, err := issue(t, v, provider)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after, _ := os.ReadFile(path)
|
||||
if !again.Unchanged || again.Generation != 1 || string(before) != string(after) || len(v.bus.raw) != said {
|
||||
t.Errorf("the same value again rewrote something: %+v", again)
|
||||
}
|
||||
|
||||
// The software changes its value: the next ask keeps it as a new generation, and says so without it.
|
||||
software = "plex-token-renewed"
|
||||
renewed, err := issue(t, v, provider)
|
||||
if err != nil || renewed.Generation != 2 {
|
||||
t.Fatalf("%+v %v", renewed, err)
|
||||
}
|
||||
if got := handedTo(t, v, "anchor.node-provisioner", "provision/anchor/plex-api"); got != software {
|
||||
t.Errorf("after the change the vault holds %q", got)
|
||||
}
|
||||
|
||||
// A value sealed to any other key is refused, and nothing changes.
|
||||
opened, _ := v.IssuedOpen(ControllerCaller, "plex-api", "anchor", "plex")
|
||||
stranger, _, _ := box.GenerateKey(rand.Reader)
|
||||
if _, err := v.IssuedTake(ControllerCaller, opened.Call, sealToCall(t, base64.StdEncoding.EncodeToString(stranger[:]), "forged")); err == nil {
|
||||
t.Error("a value not sealed to the call was taken")
|
||||
}
|
||||
|
||||
// The software rotates it, not the mesh (ADR 0305 §9); and no other path puts it into the vault (§10).
|
||||
if _, err := v.Rotate(ControllerCaller, "provision/anchor/plex-api", "person.jochen"); err == nil ||
|
||||
!strings.Contains(err.Error(), "software") {
|
||||
t.Errorf("rotate of an issued value: %v", err)
|
||||
}
|
||||
if _, err := v.AcceptOpen(ControllerCaller, "provision/anchor/plex-api", "person.jochen"); err == nil {
|
||||
t.Error("an issued value was taken by accept")
|
||||
}
|
||||
if strings.Contains(everything(t, v), "plex-token") {
|
||||
t.Fatal("the issued value is in something the vault said or wrote in the clear")
|
||||
}
|
||||
}
|
||||
|
||||
// ---- accept and rotate -------------------------------------------------------------------------------------
|
||||
|
||||
// A value given at the controller's terminal is sealed there to the vault's call and opened here alone; an
|
||||
// accepted provision's credential is never remade; a module's own given value rotates as a made one (ADR 0228).
|
||||
func TestAnAcceptedValueOpensInTheVaultAloneAndIsNotRemade(t *testing.T) {
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"provision/novox/postgres", "own/g14/dunst/token"}})
|
||||
if _, err := v.AcceptOpen("g14.claude-code", "provision/novox/postgres", ""); err == nil {
|
||||
t.Error("accept was opened for a caller that is not the controller")
|
||||
}
|
||||
for name, value := range map[string]string{"provision/novox/postgres": "pw-of-last-year", "own/g14/dunst/token": "tok"} {
|
||||
opened, err := v.AcceptOpen(ControllerCaller, name, "jochen")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
changed, err := v.AcceptTake(ControllerCaller, opened.Call, sealToCall(t, opened.SealTo, value))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if changed.By != "jochen" {
|
||||
t.Errorf("%s accepted by %q", name, changed.By)
|
||||
}
|
||||
if got := handedTo(t, v, "g14.node-provisioner", name); got != value {
|
||||
t.Errorf("%s holds %q", name, got)
|
||||
}
|
||||
rec, _, _ := v.Store.Read(name)
|
||||
if rec.Origin != OriginAccepted || rec.ChangedVia != ControllerCaller {
|
||||
t.Errorf("%s: origin %q via %q", name, rec.Origin, rec.ChangedVia)
|
||||
}
|
||||
}
|
||||
if _, err := v.Rotate(ControllerCaller, "provision/novox/postgres", "jochen"); err == nil ||
|
||||
!strings.Contains(err.Error(), "not remade") {
|
||||
t.Errorf("rotate of an accepted credential: %v", err)
|
||||
}
|
||||
rotated, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen")
|
||||
if err != nil || rotated.Generation != 2 {
|
||||
t.Fatalf("rotate of a module's own given value: %+v %v", rotated, err)
|
||||
}
|
||||
if got := handedTo(t, v, "g14.node-provisioner", "own/g14/dunst/token"); got == "tok" || len(got) != 40 {
|
||||
t.Errorf("rotated to %q", got)
|
||||
}
|
||||
if strings.Contains(everything(t, v), "pw-of-last-year") {
|
||||
t.Fatal("an accepted value is in something the vault said or wrote in the clear")
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate: refused for a secret not held and for an unproven caller; who asked is the proven caller, or the
|
||||
// person the controller names when it carries the act — both kept.
|
||||
func TestRotateKeepsWhoAskedAndWhoCarriedIt(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
if _, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen"); err == nil {
|
||||
t.Error("a secret not held was rotated")
|
||||
}
|
||||
if _, err := v.Mint("own/g14/dunst/token", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := v.Rotate("unproven", "own/g14/dunst/token", ""); err == nil {
|
||||
t.Error("an unproven caller rotated")
|
||||
}
|
||||
if _, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec, _, _ := v.Store.Read("own/g14/dunst/token")
|
||||
if rec.ChangedBy != "jochen" || rec.ChangedVia != ControllerCaller || rec.Generation != 2 {
|
||||
t.Errorf("by %q via %q generation %d", rec.ChangedBy, rec.ChangedVia, rec.Generation)
|
||||
}
|
||||
// A `by` from any caller but the controller names nobody: the proven caller is who asked.
|
||||
if _, err := v.Rotate("g14.claude-code", "own/g14/dunst/token", "jochen"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec, _, _ = v.Store.Read("own/g14/dunst/token")
|
||||
if rec.ChangedBy != "g14.claude-code" || rec.ChangedVia != "" {
|
||||
t.Errorf("by %q via %q", rec.ChangedBy, rec.ChangedVia)
|
||||
}
|
||||
}
|
||||
|
||||
// The names the vault keeps them under are names, never paths.
|
||||
func TestTheNamesOfWhatIsTakenIn(t *testing.T) {
|
||||
if n, err := OwnSecret("g14", "dunst", "token"); err != nil || n != "own/g14/dunst/token" {
|
||||
t.Errorf("%q %v", n, err)
|
||||
}
|
||||
if n, err := ProvisionSecret("novox", "postgres"); err != nil || n != "provision/novox/postgres" {
|
||||
t.Errorf("%q %v", n, err)
|
||||
}
|
||||
for _, bad := range [][3]string{{"..", "x", "y"}, {"g14", "a/b", "y"}, {"g14", "x", ""}, {"G14", "x", "y"}} {
|
||||
if _, err := OwnSecret(bad[0], bad[1], bad[2]); err == nil {
|
||||
t.Errorf("%v was taken", bad)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- the verbs as the runtime calls them ------------------------------------------------------------------
|
||||
|
||||
// Each verb takes its caller from the runtime, never from its arguments, and names its secret one way only.
|
||||
func TestTheVerbsTakeTheirCallerFromTheRuntime(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
env := &Env{Vault: v.Vault}
|
||||
defs := definitions(env)
|
||||
ask := toolNamed(t, defs, Seat+".secret-ask")
|
||||
args := map[string]any{"node": "anchor", "module": "telegram", "name": "telegram-token", "desk": "laptop",
|
||||
"asked_by": "g14.claude-code", "caller": ControllerCaller}
|
||||
if _, err := ask.Run(args); err == nil {
|
||||
t.Error("an ask opened for a caller named in the arguments")
|
||||
}
|
||||
answer, err := ask.runAs(ControllerCaller, args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opened := answer.(Opened)
|
||||
if _, err := ask.runAs(ControllerCaller, map[string]any{"call": opened.Call, "outcome": "dismissed"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
listed, err := toolNamed(t, defs, "secret_asks").Run(map[string]any{})
|
||||
if err != nil || len(listed.([]AskRecord)) != 2 {
|
||||
t.Fatalf("secret_asks: %v %v", listed, err)
|
||||
}
|
||||
|
||||
accept := toolNamed(t, defs, Seat+".accept")
|
||||
if _, err := accept.runAs(ControllerCaller, map[string]any{"node": "g14", "provision": "postgres"}); err == nil {
|
||||
t.Error("accept took a name both ways")
|
||||
}
|
||||
answer, err = accept.runAs(ControllerCaller, map[string]any{"provider": "novox", "provision": "postgres", "by": "jochen"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
o := answer.(Opened)
|
||||
if _, err := accept.runAs(ControllerCaller, map[string]any{"call": o.Call, "sealed": sealToCall(t, o.SealTo, "pw")}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rotate := toolNamed(t, defs, Seat+".rotate")
|
||||
if _, err := rotate.runAs("g14.claude-code", map[string]any{"node": "g14", "module": "dunst", "name": "token"}); err == nil {
|
||||
t.Error("a secret not held was rotated through the verb")
|
||||
}
|
||||
|
||||
issued := toolNamed(t, defs, Seat+".issued")
|
||||
answer, err = issued.runAs(ControllerCaller, map[string]any{"provision": "plex-api", "provider": "anchor", "module": "plex"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
o = answer.(Opened)
|
||||
if _, err := issued.runAs(ControllerCaller, map[string]any{"call": o.Call, "sealed": sealToCall(t, o.SealTo, "tok")}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
broken := &Env{Broken: errors.New("no store")}
|
||||
if _, err := toolNamed(t, definitions(broken), Seat+".rotate").runAs(ControllerCaller, map[string]any{}); err == nil ||
|
||||
err.Error() != "no store" {
|
||||
t.Errorf("a broken vault said %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user