diff --git a/examples/diskwipe/main.go b/examples/diskwipe/main.go index afdb314e..9bbf959f 100644 --- a/examples/diskwipe/main.go +++ b/examples/diskwipe/main.go @@ -66,18 +66,30 @@ func main() { case "nvme": wiper = utils.NewNvmeCmd(*verbose) case "sata": - // Lets see if drive supports TRIM, if so we'll use blkdiscard + // Lets figure out the drive capabilities in an easier format + var sanitize bool + var esee bool + var trim bool for _, cap := range drive.Capabilities { - if strings.HasPrefix(cap.Description, "Data Set Management TRIM supported") { - if cap.Enabled { - wiper = utils.NewBlkdiscardCmd(*verbose) - } - break + switch { + case cap.Description == "encryption supports enhanced erase": + esee = cap.Enabled + case cap.Description == "SANITIZE feature": + sanitize = cap.Enabled + case strings.HasPrefix(cap.Description, "Data Set Management TRIM supported"): + trim = cap.Enabled } } - // drive does not support TRIM so we fall back to filling it up with zero - if wiper == nil { + switch { + case sanitize || esee: + // Drive supports Sanitize or Enhanced Erase, so we use hdparm + wiper = utils.NewHdparmCmd(*verbose) + case trim: + // Drive supports TRIM, so we use blkdiscard + wiper = utils.NewBlkdiscardCmd(*verbose) + default: + // Drive does not support any preferred wipe method so we fall back to filling it up with zero wiper = utils.NewFillZeroCmd(*verbose) // If the user supplied a non-default timeout then we'll honor it, otherwise we just go with a huge timeout. diff --git a/utils/hdparm.go b/utils/hdparm.go index 4e837d9e..4a465bd2 100644 --- a/utils/hdparm.go +++ b/utils/hdparm.go @@ -2,13 +2,17 @@ package utils import ( "bufio" + "bytes" "context" + "fmt" "os" "regexp" "strings" + "time" "github.com/bmc-toolbox/common" "github.com/metal-toolbox/ironlib/model" + "github.com/sirupsen/logrus" ) const ( @@ -207,6 +211,136 @@ func (h *Hdparm) DriveCapabilities(ctx context.Context, logicalName string) ([]* return capabilities, err } +// WipeDrive implements DriveWipe by calling Sanitize or Erase as appropriate. +// Sanitize(CryptoErase) is preferred over Sanitize(BlockErase) which is preferred over Erase(CryptographicErase). +func (h *Hdparm) WipeDrive(ctx context.Context, logger *logrus.Logger, drive *common.Drive) error { // nolint:gocyclo + var ( + esee bool + eseu bool + sanitize bool + bee bool + cse bool + ) + for _, cap := range drive.Capabilities { + switch { + case cap.Description == "encryption supports enhanced erase": + esee = cap.Enabled + case cap.Description == "BLOCK ERASE EXT": + bee = cap.Enabled + case cap.Description == "CRYPTO SCRAMBLE EXT": + cse = cap.Enabled + case cap.Description == "SANITIZE feature": + sanitize = cap.Enabled + case strings.HasPrefix(cap.Description, "erase time:"): + eseu = strings.Contains(cap.Description, "enhanced") + } + } + + l := logger.WithField("drive", drive.LogicalName) + if sanitize && cse { + // nolint:govet + l := l.WithField("method", "sanitize").WithField("action", "sanitize-crypto-scramble") + l.Info("wiping") + err := h.Sanitize(ctx, drive, CryptoErase) + if err == nil { + return nil + } + l.WithError(err).Info("failed") + } + if sanitize && bee { + // nolint:govet + l := l.WithField("method", "sanitize").WithField("action", "sanitize-block-erase") + l.Info("wiping") + err := h.Sanitize(ctx, drive, BlockErase) + if err == nil { + return nil + } + l.WithError(err).Info("failed") + } + if esee && eseu { + // nolint:govet + l := l.WithField("method", "security-erase-enhanced") + l.Info("wiping") + err := h.Erase(ctx, drive, CryptographicErase) + if err == nil { + return nil + } + l.WithError(err).Info("failed") + } + return ErrIneffectiveWipe +} + +// Sanitize wipes drive using `ATA Sanitize Device` via hdparm --sanitize +func (h *Hdparm) Sanitize(ctx context.Context, drive *common.Drive, sanact SanitizeAction) error { + var sanType string + switch sanact { // nolint:exhaustive + case BlockErase: + sanType = "block-erase" + case CryptoErase: + sanType = "crypto-scramble" + default: + return fmt.Errorf("%w: %v", errSanitizeInvalidAction, sanact) + } + + verify, err := ApplyWatermarks(drive) + if err != nil { + return err + } + + h.Executor.SetArgs("--yes-i-know-what-i-am-doing", "--sanitize-"+sanType, drive.LogicalName) + _, err = h.Executor.Exec(ctx) + if err != nil { + return err + } + + // now we loop until --sanitize-status reports that sanitization is complete + for { + h.Executor.SetArgs("--sanitize-status", drive.LogicalName) + result, err := h.Executor.Exec(ctx) + if err != nil { + return err + } + if h.sanitizeDone(result.Stdout) { + break + } + time.Sleep(100 * time.Millisecond) + } + + return verify() +} + +func (h *Hdparm) sanitizeDone(output []byte) bool { + return bytes.Contains(output, []byte("Sanitize Idle")) +} + +// Erase wipes drive using ATA Secure Erase via hdparm --security-erase-enhanced +func (h *Hdparm) Erase(ctx context.Context, drive *common.Drive, ses SecureEraseSetting) error { + switch ses { // nolint:exhaustive + case CryptographicErase: + default: + return fmt.Errorf("%w: %v", errFormatInvalidSetting, ses) + } + + h.Executor.SetArgs("--user-master", "u", "--security-set-pass", "p", drive.LogicalName) + _, err := h.Executor.Exec(ctx) + if err != nil { + return err + } + + verify, err := ApplyWatermarks(drive) + if err != nil { + return err + } + + h.Executor.SetArgs("--user-master", "u", "--security-erase-enhanced", "p", drive.LogicalName) + _, err = h.Executor.Exec(ctx) + if err != nil { + return err + } + + return verify() +} + // NewFakeHdparm returns a mock hdparm collector that returns mock data for use in tests. func NewFakeHdparm() *Hdparm { return &Hdparm{ diff --git a/utils/hdparm_test.go b/utils/hdparm_test.go index 10e9b0bd..a3d70d1b 100644 --- a/utils/hdparm_test.go +++ b/utils/hdparm_test.go @@ -293,3 +293,61 @@ var fixtureHdparmDeviceCapabilities = []*common.Capability{ Enabled: false, }, } + +func Test_HdparmSanitizeDone(t *testing.T) { + // note the order is not messed up, this is indeed what hdparm showed me + tests := []struct { + name string + done bool + in string + }{ + { + name: "5%", + in: ` +/dev/sdb: +Issuing SANITIZE_STATUS command +Sanitize status: + State: SD2 Sanitize operation In Process + Progress: 0xf5a (5%) +`, + }, + { + name: "1%", + in: ` +/dev/sdb: +Issuing SANITIZE_STATUS command +Sanitize status: + State: SD2 Sanitize operation In Process + Progress: 0x28f (1%) +`, + }, + { + name: "3%", + in: ` +/dev/sdb: +Issuing SANITIZE_STATUS command +Sanitize status: + State: SD2 Sanitize operation In Process + Progress: 0xa3c (3%) +`, + }, + { + name: "done", + done: true, + in: ` +/dev/sdb: +Issuing SANITIZE_STATUS command +Sanitize status: + State: SD0 Sanitize Idle + Last Sanitize Operation Completed Without Error +`, + }, + } + + var h Hdparm + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.done, h.sanitizeDone([]byte(test.in))) + }) + } +}