From 9420fa9fc52382aead0432fb3733684f24db3342 Mon Sep 17 00:00:00 2001 From: Ethan Adams Date: Tue, 3 Dec 2019 16:09:39 -0600 Subject: [PATCH] satellite/gracefulexit: Add graceful exit completed/failed receipt verification to satellite CLI (#3679) --- cmd/satellite/gracefulexit.go | 71 ++++++++++++++++++++++++++++++ cmd/satellite/main.go | 32 +++++++++++++- storagenode/gracefulexit/worker.go | 14 +++++- 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/cmd/satellite/gracefulexit.go b/cmd/satellite/gracefulexit.go index 246bd8e88..5fd0aede7 100644 --- a/cmd/satellite/gracefulexit.go +++ b/cmd/satellite/gracefulexit.go @@ -6,15 +6,21 @@ package main import ( "context" "encoding/csv" + "encoding/hex" "fmt" "io" "os" "strconv" + "text/tabwriter" "time" + "github.com/gogo/protobuf/proto" "github.com/zeebo/errs" "go.uber.org/zap" + "storj.io/storj/pkg/identity" + "storj.io/storj/pkg/pb" + "storj.io/storj/pkg/signing" "storj.io/storj/pkg/storj" "storj.io/storj/satellite/gracefulexit" "storj.io/storj/satellite/satellitedb" @@ -98,3 +104,68 @@ func generateGracefulExitCSV(ctx context.Context, completed bool, start time.Tim } return err } + +func verifyGracefulExitReceipt(ctx context.Context, identity *identity.FullIdentity, nodeID storj.NodeID, receipt string) error { + signee := signing.SigneeFromPeerIdentity(identity.PeerIdentity()) + + bytes, err := hex.DecodeString(receipt) + if err != nil { + return errs.Wrap(err) + } + + // try to unmarshal as an ExitCompleted first + completed := &pb.ExitCompleted{} + err = proto.Unmarshal(bytes, completed) + if err != nil { + // if it is not a ExitCompleted, try ExitFailed + failed := &pb.ExitFailed{} + err = proto.Unmarshal(bytes, failed) + if err != nil { + return errs.Wrap(err) + } + + err = checkIDs(identity.PeerIdentity().ID, nodeID, failed.SatelliteId, failed.NodeId) + if err != nil { + return err + } + + err = signing.VerifyExitFailed(ctx, signee, failed) + if err != nil { + return errs.Wrap(err) + } + + return writeVerificationMessage(false, failed.SatelliteId, failed.NodeId, failed.Failed) + } + + err = checkIDs(identity.PeerIdentity().ID, nodeID, completed.SatelliteId, completed.NodeId) + if err != nil { + return err + } + + err = signing.VerifyExitCompleted(ctx, signee, completed) + if err != nil { + return errs.Wrap(err) + } + + return writeVerificationMessage(true, completed.SatelliteId, completed.NodeId, completed.Completed) +} + +func checkIDs(satelliteID storj.NodeID, providedSNID storj.NodeID, receiptSatelliteID storj.NodeID, receiptSNID storj.NodeID) error { + if satelliteID != receiptSatelliteID { + return errs.New("satellite ID (%v) does not match receipt satellite ID (%v).", satelliteID, receiptSatelliteID) + } + if providedSNID != receiptSNID { + return errs.New("provided storage node ID (%v) does not match receipt node ID (%v).", providedSNID, receiptSNID) + } + return nil +} + +func writeVerificationMessage(succeeded bool, satelliteID storj.NodeID, snID storj.NodeID, timestamp time.Time) error { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "Succeeded:\t%v\n", succeeded) + fmt.Fprintf(w, "Satellite ID:\t%v\n", satelliteID) + fmt.Fprintf(w, "Storage Node ID:\t%v\n", snID) + fmt.Fprintf(w, "Timestamp:\t%v\n", timestamp) + + return errs.Wrap(w.Flush()) +} diff --git a/cmd/satellite/main.go b/cmd/satellite/main.go index 2c599ceac..afb17512d 100644 --- a/cmd/satellite/main.go +++ b/cmd/satellite/main.go @@ -20,6 +20,7 @@ import ( "storj.io/storj/pkg/cfgstruct" "storj.io/storj/pkg/process" "storj.io/storj/pkg/revocation" + "storj.io/storj/pkg/storj" "storj.io/storj/private/fpath" "storj.io/storj/private/version" "storj.io/storj/satellite" @@ -97,6 +98,14 @@ var ( RunE: cmdGracefulExit, } + verifyGracefulExitReceiptCmd = &cobra.Command{ + Use: "verify-exit-receipt [storage node ID] [receipt]", + Short: "Verify a graceful exit receipt", + Long: "Verify a graceful exit receipt is valid.", + Args: cobra.MinimumNArgs(2), + RunE: cmdVerifyGracefulExitReceipt, + } + runCfg Satellite setupCfg Satellite @@ -113,10 +122,12 @@ var ( Output string `help:"destination of report output" default:""` } gracefulExitCfg struct { - Database string `help:"satellite database connection string" releaseDefault:"postgres://" devDefault:"sqlite3://$CONFDIR/master.db"` + Database string `help:"satellite database connection string" releaseDefault:"postgres://" devDefault:"postgres://"` Output string `help:"destination of report output" default:""` Completed bool `help:"whether to output (initiated and completed) or (initiated and not completed)" default:"false"` } + verifyGracefulExitReceiptCfg struct { + } confDir string identityDir string ) @@ -137,6 +148,7 @@ func init() { reportsCmd.AddCommand(nodeUsageCmd) reportsCmd.AddCommand(partnerAttributionCmd) reportsCmd.AddCommand(gracefulExitCmd) + reportsCmd.AddCommand(verifyGracefulExitReceiptCmd) process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) process.Bind(runMigrationCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) process.Bind(runAPICmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) @@ -145,6 +157,7 @@ func init() { process.Bind(qdiagCmd, &qdiagCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) process.Bind(nodeUsageCmd, &nodeUsageCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) process.Bind(gracefulExitCmd, &gracefulExitCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) + process.Bind(verifyGracefulExitReceiptCmd, &verifyGracefulExitReceiptCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) process.Bind(partnerAttributionCmd, &partnerAttribtionCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) } @@ -287,6 +300,23 @@ func cmdQDiag(cmd *cobra.Command, args []string) (err error) { return w.Flush() } +func cmdVerifyGracefulExitReceipt(cmd *cobra.Command, args []string) (err error) { + ctx, _ := process.Ctx(cmd) + + identity, err := runCfg.Identity.Load() + if err != nil { + zap.S().Fatal(err) + } + + // Check the node ID is valid + nodeID, err := storj.NodeIDFromString(args[0]) + if err != nil { + return errs.Combine(err, errs.New("Invalid node ID.")) + } + + return verifyGracefulExitReceipt(ctx, identity, nodeID, args[1]) +} + func cmdGracefulExit(cmd *cobra.Command, args []string) (err error) { ctx, _ := process.Ctx(cmd) diff --git a/storagenode/gracefulexit/worker.go b/storagenode/gracefulexit/worker.go index 57e209b89..6ffa6f534 100644 --- a/storagenode/gracefulexit/worker.go +++ b/storagenode/gracefulexit/worker.go @@ -10,6 +10,7 @@ import ( "os" "time" + "github.com/gogo/protobuf/proto" "github.com/zeebo/errs" "go.uber.org/zap" @@ -122,13 +123,22 @@ func (worker *Worker) Run(ctx context.Context, done func()) (err error) { zap.Stringer("Satellite ID", worker.satelliteID), zap.Stringer("reason", msg.ExitFailed.Reason)) - err = worker.satelliteDB.CompleteGracefulExit(ctx, worker.satelliteID, time.Now(), satellites.ExitFailed, msg.ExitFailed.GetExitFailureSignature()) + exitFailedBytes, err := proto.Marshal(msg.ExitFailed) + if err != nil { + worker.log.Error("failed to marshal exit failed message.") + } + err = worker.satelliteDB.CompleteGracefulExit(ctx, worker.satelliteID, time.Now(), satellites.ExitFailed, exitFailedBytes) return errs.Wrap(err) case *pb.SatelliteMessage_ExitCompleted: worker.log.Info("graceful exit completed.", zap.Stringer("Satellite ID", worker.satelliteID)) - err = worker.satelliteDB.CompleteGracefulExit(ctx, worker.satelliteID, time.Now(), satellites.ExitSucceeded, msg.ExitCompleted.GetExitCompleteSignature()) + exitCompletedBytes, err := proto.Marshal(msg.ExitCompleted) + if err != nil { + worker.log.Error("failed to marshal exit completed message.") + } + + err = worker.satelliteDB.CompleteGracefulExit(ctx, worker.satelliteID, time.Now(), satellites.ExitSucceeded, exitCompletedBytes) if err != nil { return errs.Wrap(err) }