Recoverable means sealed to the current operator key; recovery names the provider
From review: the export counted any operator-sealed row as recoverable, so a secret sealed to a replaced key was reported as openable with the current one; replacing the key counted orphans in one table of two; and a pair credential held from two providers was recovered as whichever row came first. The export now lists what the current key opens, what an earlier key opens, and what has no copy; `secret recover` takes --provider and refuses ambiguity; files that must not exist are created exclusively; one constructor builds the export for the operator's file and the vault's disk alike.
This commit is contained in:
@@ -2,6 +2,7 @@ package inventory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/catalogue"
|
||||
@@ -20,10 +21,10 @@ 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)
|
||||
kept, _, unrecoverable, err := inv.KeptForOperator(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -46,14 +47,14 @@ func TestAnOwnSecretIsSealedToTheOperatorToo(t *testing.T) {
|
||||
if err := inv.AcceptSecretForModule(ctx, "provider", "postgres", "replication", "given-by-a-person"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
kept, unrecoverable, err = inv.KeptForOperator(ctx)
|
||||
kept, _, unrecoverable, err = inv.KeptForOperator(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -67,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)
|
||||
}
|
||||
@@ -75,26 +76,59 @@ func TestAnOwnSecretIsSealedToTheOperatorToo(t *testing.T) {
|
||||
t.Fatalf("the minted secret did not open to a 40-character value: %v", err)
|
||||
}
|
||||
|
||||
// The old secret, remade for a rejoined node, becomes recoverable — it was issued again.
|
||||
// The old secret is kept, not resealed: asking again is a read, the plaintext is gone, and it
|
||||
// stays honestly unrecoverable.
|
||||
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
|
||||
// sealed to the operator — the one scenario the vault exists for.
|
||||
rejoined, err := inv.NodeByName(ctx, "consumer")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newKey, _ := aSealingKey(t)
|
||||
if err := inv.RecordSealingKey(ctx, rejoined.ID, newKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.SecretForModule(ctx, "consumer", "postgres", "superuser"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
remade, err := inv.KeptSecret(ctx, "consumer", "postgres", "superuser", "")
|
||||
if err != nil {
|
||||
t.Fatalf("the remade secret is not recoverable: %v", err)
|
||||
}
|
||||
if _, err := secrets.Open(priv, remade.Sealed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replacing the key says how many secrets stay sealed to the old one.
|
||||
// Replacing the key says how many secrets stay sealed to the old one — and those move out of
|
||||
// the recoverable list, whatever the export is labelled with.
|
||||
pub2, _, _ := secrets.Keypair()
|
||||
orphaned, err := inv.SetOperatorKey(ctx, pub2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if orphaned != 2 {
|
||||
t.Fatalf("replacing the key orphaned %d, and two were sealed to it", orphaned)
|
||||
if orphaned != 3 {
|
||||
t.Fatalf("replacing the key orphaned %d, and three were sealed to it", orphaned)
|
||||
}
|
||||
if now, _ := inv.OperatorKey(ctx); now != pub2 {
|
||||
t.Fatal("the new key is not the mesh's key")
|
||||
}
|
||||
kept, earlier, _, err := inv.KeptForOperator(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(kept) != 0 || len(earlier) != 3 {
|
||||
t.Fatalf("after replacing the key: %d recoverable with it, %d sealed to the earlier key", len(kept), len(earlier))
|
||||
}
|
||||
doc, err := inv.OperatorExport(ctx)
|
||||
if err != nil || doc == nil || len(doc.EarlierKey) != 3 || len(doc.Kept) != 0 {
|
||||
t.Fatalf("the export does not say what the current key cannot open: %+v %v", doc, err)
|
||||
}
|
||||
}
|
||||
|
||||
// A pair credential — what the vault provides a module — is sealed to the operator too, and the
|
||||
@@ -134,13 +168,42 @@ 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)
|
||||
}
|
||||
if kept.Kind != "pair" || kept.Provider != "provider" {
|
||||
t.Fatalf("kept as %+v", kept)
|
||||
}
|
||||
all, _, _, err := inv.KeptForOperator(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var pairs int
|
||||
for _, k := range all {
|
||||
if k.Kind == "pair" {
|
||||
pairs++
|
||||
}
|
||||
}
|
||||
if pairs != 1 {
|
||||
t.Fatalf("%d pair credential(s) recoverable, expected 1", pairs)
|
||||
}
|
||||
|
||||
// A second provider of the same provision: two rows, refused rather than the first one taken,
|
||||
// unless the provider is named. And replacing the key counts pair credentials as orphaned.
|
||||
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") {
|
||||
t.Fatalf("two providers were not refused: %v", err)
|
||||
}
|
||||
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()
|
||||
if orphaned, err := inv.SetOperatorKey(ctx, pub2); err != nil || orphaned != 2 {
|
||||
t.Fatalf("replacing the key orphaned %d pair credential(s), and two were sealed to it (%v)", orphaned, err)
|
||||
}
|
||||
fromOperator, err := secrets.Open(priv, kept.Sealed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -153,17 +216,4 @@ func TestAPairCredentialIsSealedToTheOperatorToo(t *testing.T) {
|
||||
if string(fromOperator) != string(fromNode) {
|
||||
t.Fatal("the operator's copy of the pair credential differs from the consumer's")
|
||||
}
|
||||
all, _, err := inv.KeptForOperator(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var pairs int
|
||||
for _, k := range all {
|
||||
if k.Kind == "pair" {
|
||||
pairs++
|
||||
}
|
||||
}
|
||||
if pairs != 1 {
|
||||
t.Fatalf("%d pair credential(s) in the export, expected 1", pairs)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user