package cmd import ( "context" "fmt" "gitea.hostxtra.co.uk/vantage/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", 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() // The key is read unconditionally. Archive-only mode - no MONGO_URI, // which is what a scheduled check uses - must still compare the key // in hand against the archive's fingerprint; leaving it unset there // reported "KEY_ENCRYPTION_KEY is not set" for a key that was set // and correct. opt := backup.VerifyOptions{Archive: archive, KeyHex: keyFromEnv()} // A database is optional here. resolveGlobals fails without a URI or // without a resolvable database name, and either 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 } else { fmt.Fprintf(c.ErrOrStderr(), "note: %v, so this checks the archive and the key only\n", gerr) } 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") }, } }