Review: one local name is still a local name; a local name is unique; recovery knows it; recipes read as instructions; a tag before a digest; ask fails at once when nothing serves

A secrets object with one local name delivered no file. Two requirements could share
a local name. secret recover and the export could not tell two locals apart. The
recipe check missed continued lines and read heredoc bodies as bases. repo:tag@digest
kept the tag in the repository. ask now publishes mandatory, so a tool nothing serves
is said at once rather than after the wait.
This commit is contained in:
2026-09-21 21:03:22 +02:00
parent e81f352979
commit 9f3790dcda
13 changed files with 229 additions and 29 deletions
+11 -10
View File
@@ -124,20 +124,20 @@ func (i *Inventory) KeptForOperator(ctx context.Context) (kept, earlier, unrecov
}
rows, err := i.store.Pool().Query(ctx,
`select 'own', n.name, s.module, s.name, '', s.origin, coalesce(s.operator_sealed, ''),
coalesce(s.operator_key, ''), s.made_at
coalesce(s.operator_key, ''), s.made_at, ''
from module_secret s join node n on n.id = s.node
union all
select 'pair', c.name, s.consumer_module, s.name, p.name, 'made', coalesce(s.operator_sealed, ''),
coalesce(s.operator_key, ''), s.created_at
select 'pair', c.name, s.consumer_module, s.name, p.name, s.origin, coalesce(s.operator_sealed, ''),
coalesce(s.operator_key, ''), s.created_at, s.local
from secret s join node c on c.id = s.consumer join node p on p.id = s.provider
order by 1, 2, 3, 4`)
order by 1, 2, 3, 4, 10`)
if err != nil {
return nil, nil, nil, err
}
defer rows.Close()
for rows.Next() {
var k Kept
if err := rows.Scan(&k.Kind, &k.Node, &k.Module, &k.Name, &k.Provider, &k.Origin, &k.Sealed, &k.Key, &k.MadeAt); err != nil {
if err := rows.Scan(&k.Kind, &k.Node, &k.Module, &k.Name, &k.Provider, &k.Origin, &k.Sealed, &k.Key, &k.MadeAt, &k.Local); err != nil {
return nil, nil, nil, err
}
switch {
@@ -159,7 +159,7 @@ func (i *Inventory) KeptForOperator(ctx context.Context) (kept, earlier, unrecov
// credential is keyed by provider as well, and a consumer whose provision moved leaves the old
// provider's row behind: two rows is refused with both providers named, never answered with
// whichever came first, unless `provider` says which.
func (i *Inventory) KeptSecret(ctx context.Context, node, module, name, provider string) (Kept, error) {
func (i *Inventory) KeptSecret(ctx context.Context, node, module, name, provider, local string) (Kept, error) {
var k Kept
err := i.store.Pool().QueryRow(ctx,
`select 'own', n.name, s.module, s.name, '', s.origin, coalesce(s.operator_sealed, ''),
@@ -169,11 +169,12 @@ func (i *Inventory) KeptSecret(ctx context.Context, node, module, name, provider
Scan(&k.Kind, &k.Node, &k.Module, &k.Name, &k.Provider, &k.Origin, &k.Sealed, &k.Key, &k.MadeAt)
if errors.Is(err, pgx.ErrNoRows) {
rows, qerr := i.store.Pool().Query(ctx,
`select 'pair', c.name, s.consumer_module, s.name, p.name, 'made', coalesce(s.operator_sealed, ''),
coalesce(s.operator_key, ''), s.created_at
`select 'pair', c.name, s.consumer_module, s.name, p.name, s.origin, coalesce(s.operator_sealed, ''),
coalesce(s.operator_key, ''), s.created_at, s.local
from secret s join node c on c.id = s.consumer join node p on p.id = s.provider
where c.name = $1 and s.consumer_module = $2 and s.name = $3 and ($4 = '' or p.name = $4)
order by p.name`, node, module, name, provider)
and s.local = $5
order by p.name`, node, module, name, provider, local)
if qerr != nil {
return Kept{}, qerr
}
@@ -181,7 +182,7 @@ func (i *Inventory) KeptSecret(ctx context.Context, node, module, name, provider
var found []Kept
for rows.Next() {
var row Kept
if err := rows.Scan(&row.Kind, &row.Node, &row.Module, &row.Name, &row.Provider, &row.Origin, &row.Sealed, &row.Key, &row.MadeAt); err != nil {
if err := rows.Scan(&row.Kind, &row.Node, &row.Module, &row.Name, &row.Provider, &row.Origin, &row.Sealed, &row.Key, &row.MadeAt, &row.Local); err != nil {
return Kept{}, err
}
found = append(found, row)
+8 -8
View File
@@ -21,7 +21,7 @@ func TestAnOwnSecretIsSealedToTheOperatorToo(t *testing.T) {
if _, err := inv.SecretForModule(ctx, "consumer", "postgres", "superuser"); err != nil {
t.Fatal(err)
}
if _, err := inv.KeptSecret(ctx, "consumer", "postgres", "superuser", ""); err == nil {
if _, err := inv.KeptSecret(ctx, "consumer", "postgres", "superuser", "", ""); err == nil {
t.Fatal("a secret made before the operator key was reported recoverable")
}
kept, _, unrecoverable, err := inv.KeptForOperator(ctx)
@@ -54,7 +54,7 @@ func TestAnOwnSecretIsSealedToTheOperatorToo(t *testing.T) {
if len(kept) != 2 || len(unrecoverable) != 1 {
t.Fatalf("after a key: %d kept, %d unrecoverable", len(kept), len(unrecoverable))
}
got, err := inv.KeptSecret(ctx, "provider", "postgres", "replication", "")
got, err := inv.KeptSecret(ctx, "provider", "postgres", "replication", "", "")
if err != nil {
t.Fatal(err)
}
@@ -68,7 +68,7 @@ func TestAnOwnSecretIsSealedToTheOperatorToo(t *testing.T) {
if got.Origin != "accepted" || got.Key != pub {
t.Fatalf("kept as %+v", got)
}
minted, err := inv.KeptSecret(ctx, "provider", "postgres", "superuser", "")
minted, err := inv.KeptSecret(ctx, "provider", "postgres", "superuser", "", "")
if err != nil {
t.Fatal(err)
}
@@ -81,7 +81,7 @@ func TestAnOwnSecretIsSealedToTheOperatorToo(t *testing.T) {
if _, err := inv.SecretForModule(ctx, "consumer", "postgres", "superuser"); err != nil {
t.Fatal(err)
}
if _, err := inv.KeptSecret(ctx, "consumer", "postgres", "superuser", ""); err == nil {
if _, err := inv.KeptSecret(ctx, "consumer", "postgres", "superuser", "", ""); err == nil {
t.Fatal("asking again did not remake, yet it became recoverable")
}
// Until the node rejoins with a new sealing key: then the secret is remade, and the remake is
@@ -97,7 +97,7 @@ func TestAnOwnSecretIsSealedToTheOperatorToo(t *testing.T) {
if _, err := inv.SecretForModule(ctx, "consumer", "postgres", "superuser"); err != nil {
t.Fatal(err)
}
remade, err := inv.KeptSecret(ctx, "consumer", "postgres", "superuser", "")
remade, err := inv.KeptSecret(ctx, "consumer", "postgres", "superuser", "", "")
if err != nil {
t.Fatalf("the remade secret is not recoverable: %v", err)
}
@@ -168,7 +168,7 @@ func TestAPairCredentialIsSealedToTheOperatorToo(t *testing.T) {
if err != nil {
t.Fatal(err)
}
kept, err := inv.KeptSecret(ctx, "consumer", "gitea", "secret", "")
kept, err := inv.KeptSecret(ctx, "consumer", "gitea", "secret", "", "")
if err != nil {
t.Fatal(err)
}
@@ -194,10 +194,10 @@ func TestAPairCredentialIsSealedToTheOperatorToo(t *testing.T) {
if _, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "consumer", ""); err != nil {
t.Fatal(err)
}
if _, err := inv.KeptSecret(ctx, "consumer", "gitea", "secret", ""); err == nil || !strings.Contains(err.Error(), "--provider") {
if _, err := inv.KeptSecret(ctx, "consumer", "gitea", "secret", "", ""); err == nil || !strings.Contains(err.Error(), "--provider") {
t.Fatalf("two providers were not refused: %v", err)
}
if byName, err := inv.KeptSecret(ctx, "consumer", "gitea", "secret", "provider"); err != nil || byName.Provider != "provider" {
if byName, err := inv.KeptSecret(ctx, "consumer", "gitea", "secret", "provider", ""); err != nil || byName.Provider != "provider" {
t.Fatalf("naming the provider did not select it: %+v %v", byName, err)
}
pub2, _, _ := secrets.Keypair()
+42
View File
@@ -6,6 +6,7 @@ import (
"crypto/rand"
"encoding/base64"
"github.com/novox/mesh-controller/internal/catalogue"
"github.com/novox/mesh-controller/internal/secrets"
"strings"
"testing"
@@ -673,3 +674,44 @@ func TestTwoLocalNamesAreTwoCredentials(t *testing.T) {
t.Fatalf("the provider is told two credentials to create: %+v", from)
}
}
// The operator can recover either of two local names apart (review C3).
func TestTheOperatorRecoversEachLocalNameApart(t *testing.T) {
inv, ctx := twoNodesWithKeys(t)
pub, _, err := secrets.Keypair()
if err != nil {
t.Fatal(err)
}
if _, err := inv.SetOperatorKey(ctx, pub); err != nil {
t.Fatal(err)
}
for _, local := range []string{"root-key", "root-pass"} {
if _, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", local); err != nil {
t.Fatal(err)
}
}
key, err := inv.KeptSecret(ctx, "consumer", "gitea", "secret", "provider", "root-key")
if err != nil {
t.Fatal(err)
}
pass, err := inv.KeptSecret(ctx, "consumer", "gitea", "secret", "provider", "root-pass")
if err != nil {
t.Fatal(err)
}
if key.Local != "root-key" || pass.Local != "root-pass" || key.Kind != "pair" {
t.Fatalf("recovery does not tell the two apart: %+v %+v", key, pass)
}
kept, _, _, err := inv.KeptForOperator(ctx)
if err != nil {
t.Fatal(err)
}
var locals []string
for _, k := range kept {
if k.Kind == "pair" {
locals = append(locals, k.Local)
}
}
if strings.Join(locals, ",") != "root-key,root-pass" {
t.Fatalf("the export does not name the local names: %v", kept)
}
}