Files
vantage-ctl/internal/cmd/verify.go
T

95 lines
3.0 KiB
Go

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")
},
}
}