feat: Add the vantagectl restore and verify subcommands
--force requires a typed database name on a terminal and --confirm-db without one, so a copy-pasted restore command carries its intended target and cannot destroy a different database. Also silences cobra's own error print (root.go) so a failure is reported once by main.go instead of twice, and pins the Changed()-based env fallback in resolveGlobals with a test for an explicitly empty --db.
This commit is contained in:
+85
-2
@@ -1,7 +1,90 @@
|
||||
package cmd
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.hostxtra.co.uk/mrhid6/vantage/shared/backup"
|
||||
"github.com/spf13/cobra"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
)
|
||||
|
||||
func newVerifyCmd() *cobra.Command {
|
||||
return &cobra.Command{Use: "verify ARCHIVE", Short: "Check an archive against the key in hand"}
|
||||
return &cobra.Command{
|
||||
Use: "verify ARCHIVE",
|
||||
Short: "Check an archive against the key in hand",
|
||||
Long: "verify checks that an archive is intact and that the\n" +
|
||||
"KEY_ENCRYPTION_KEY in this environment matches the one it was made\n" +
|
||||
"with.\n\n" +
|
||||
"Given --mongo-uri it goes further and opens a real ciphertext value\n" +
|
||||
"from that database. A fingerprint proves two archives agree about a\n" +
|
||||
"key; only the probe proves the key you hold reads the data.\n\n" +
|
||||
"Exit status is non-zero when anything is wrong, so this is the command\n" +
|
||||
"to put on a schedule.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
ctx := c.Context()
|
||||
|
||||
archive, err := backup.Open(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer archive.Close()
|
||||
|
||||
opt := backup.VerifyOptions{Archive: archive}
|
||||
|
||||
// A database is optional here. resolveGlobals fails without a URI,
|
||||
// so its error is a signal to verify the archive alone rather than
|
||||
// a reason to stop.
|
||||
var client *mongo.Client
|
||||
if g, gerr := resolveGlobals(c); gerr == nil {
|
||||
client, err = connect(ctx, g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Disconnect(context.Background())
|
||||
opt.Client = client
|
||||
opt.Database = g.Database
|
||||
opt.KeyHex = g.KeyHex
|
||||
} else {
|
||||
fmt.Fprintln(c.ErrOrStderr(),
|
||||
"note: no MongoDB URI, so this checks the archive and the key only")
|
||||
}
|
||||
|
||||
rep, err := backup.Verify(ctx, opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out := c.OutOrStdout()
|
||||
fmt.Fprintln(out, "Archive intact, every member matches its checksum")
|
||||
if rep.ArchiveFingerprint != nil {
|
||||
fmt.Fprintf(out, "Archive key %s\n", *rep.ArchiveFingerprint)
|
||||
}
|
||||
if rep.KeyFingerprint != nil {
|
||||
fmt.Fprintf(out, "Your key %s\n", *rep.KeyFingerprint)
|
||||
}
|
||||
if rep.KeyMatchesArchive {
|
||||
fmt.Fprintln(out, "Key match yes")
|
||||
}
|
||||
switch {
|
||||
case rep.ProbeDecrypted:
|
||||
fmt.Fprintf(out, "Live probe decrypted a value from %s\n", rep.ProbeCollection)
|
||||
case rep.ProbeAttempted:
|
||||
fmt.Fprintf(out, "Live probe FAILED against %s\n", rep.ProbeCollection)
|
||||
case opt.Client != nil:
|
||||
fmt.Fprintln(out, "Live probe skipped; this database stores no ciphertext yet")
|
||||
}
|
||||
|
||||
if rep.OK() {
|
||||
fmt.Fprintln(out, "\nThis archive will restore.")
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintln(out)
|
||||
for _, p := range rep.Problems {
|
||||
fmt.Fprintln(out, "problem:", p)
|
||||
}
|
||||
return fmt.Errorf("verification failed")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user