Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for hdparm based drive wipe #167

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions examples/diskwipe/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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":
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down
134 changes: 134 additions & 0 deletions utils/hdparm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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":
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
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")
mmlb marked this conversation as resolved.
Show resolved Hide resolved
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)
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
_, err = h.Executor.Exec(ctx)
if err != nil {
return err
}

// now we loop until --sanitize-status reports that sanitization is complete
for {
mmlb marked this conversation as resolved.
Show resolved Hide resolved
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{
Expand Down
58 changes: 58 additions & 0 deletions utils/hdparm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
})
}
}
Loading