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:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user