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

ironlib is able to detect ineffective wipes #135

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
23 changes: 21 additions & 2 deletions actions/storage_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ func (s *StorageControllerAction) WipeDisk(ctx context.Context, logicalName stri
if err != nil {
return err
}

return util.WipeDisk(ctx, logicalName)
// Watermark disk
turegano-equinix marked this conversation as resolved.
Show resolved Hide resolved
// Before wiping the disk, we apply watermarks to later verify successful deletion
log.Printf("%s | Initiating watermarking process", logicalName)
check, err := utils.ApplyWatermarks(logicalName)
if err != nil {
return err
}
// Wipe the disk
err = util.WipeDisk(ctx, logicalName)
if err != nil {
return err
}
// Check if the watermark has been removed after wiping
log.Printf("%s | Checking if the watermark has been removed", logicalName)
err = check()
if err != nil {
return err
}
// Watermarks have been successfully removed, indicating successful deletion
log.Printf("%s | Watermarks has been removed", logicalName)
return nil
}
3 changes: 1 addition & 2 deletions examples/diskwipe/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"context"
"fmt"
"time"

"github.com/metal-toolbox/ironlib/actions"
Expand All @@ -22,5 +21,5 @@ func main() {
if err != nil {
logger.Fatal(err)
}
fmt.Println("Wiped")
logger.Println("Wiped successfully!")
}
109 changes: 109 additions & 0 deletions utils/watermark_disk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package utils

import (
"crypto/rand"
"fmt"
"io"
"math/big"
"os"
"slices"

"github.com/pkg/errors"
)

const (
bufferSize = 512
numWatermarks = 10
)

type watermark struct {
position int64
data []byte
}

// ApplyWatermarks applies watermarks to the specified disk.
// It returns a function that checks if the applied watermarks still exist on the file.
// It relies on the writeWatermarks function to uniformly write watermarks across the disk.
func ApplyWatermarks(logicalName string) (func() error, error) {
turegano-equinix marked this conversation as resolved.
Show resolved Hide resolved
// Write open
file, err := os.OpenFile(logicalName, os.O_WRONLY, 0)
if err != nil {
return nil, err
}
defer file.Close()

// Get disk or partition size
fileSize, err := file.Seek(0, io.SeekEnd)
if err != nil {
return nil, err
}

if fileSize == 0 {
return nil, errors.New("No space for watermarking")
}

// Write watermarks on random locations
watermarks, err := writeWatermarks(file, fileSize, numWatermarks)
if err != nil {
return nil, err
}

checker := func() error {
file, err := os.OpenFile(logicalName, os.O_RDONLY, 0)
if err != nil {
return err
}
defer file.Close()

for _, watermark := range watermarks {
_, err = file.Seek(watermark.position, io.SeekStart)
if err != nil {
return err
}
// Read the watermark written to the position
currentValue := make([]byte, bufferSize)
_, err = io.ReadFull(file, currentValue)
if err != nil {
return err
}
// Check if the watermark is still in the disk
if slices.Equal(currentValue, watermark.data) {
ErrorExistingWatermark := errors.New("Error existing watermark in the position: ")
return fmt.Errorf("%s@%d | %w", logicalName, watermark.position, ErrorExistingWatermark)
}
}
return nil
}
return checker, nil
}

// writeWatermarks creates random watermarks and writes them uniformly into a given file.
func writeWatermarks(file *os.File, fileSize, count int64) ([]watermark, error) {
origin := int64(0)
intervalSize := fileSize / count
watermarks := make([]watermark, count)
for i := 0; i < numWatermarks; i++ {
data := make([]byte, bufferSize)
_, err := rand.Read(data)
if err != nil {
return nil, err
}
offset, err := rand.Int(rand.Reader, big.NewInt(intervalSize))
if err != nil {
return nil, err
}
randomPosition := int64(offset.Uint64()) + origin - bufferSize
_, err = file.Seek(randomPosition, io.SeekStart)
if err != nil {
return nil, err
}
_, err = file.Write(data)
if err != nil {
return nil, err
}
watermarks[i].position = randomPosition
watermarks[i].data = data
origin += intervalSize
}
return watermarks, nil
}
70 changes: 70 additions & 0 deletions utils/watermark_disk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package utils

import (
"crypto/rand"
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func Test_ApplyWatermarks(t *testing.T) {
// Create a temporary file
tempFile, err := os.CreateTemp("", "testfile")
if err != nil {
t.Fatalf("Failed to create temporary file: %v", err)
}
defer os.Remove(tempFile.Name())

// Close the file since we'll be reopening it in ApplyWatermarks
tempFile.Close()

t.Run("NegativeTest", func(t *testing.T) {
// Create a ~15KB empty file, no room for all watermarks
err := os.WriteFile(tempFile.Name(), make([]byte, 1*1024), 0o600)
if err != nil {
t.Fatalf("Failed to create empty file: %v", err)
}
turegano-equinix marked this conversation as resolved.
Show resolved Hide resolved
// Create a 1KB empty file, no room for all watermarks
assert.NoError(t, os.Truncate(tempFile.Name(), 1*1024))
// Apply watermarks and expect an error
checker, _ := ApplyWatermarks(tempFile.Name())
assert.Nil(t, checker)
})

t.Run("EmptyFile", func(t *testing.T) {
// Wipe the file
assert.NoError(t, os.Truncate(tempFile.Name(), 0))

// Apply watermarks and expect no error
checker, err := ApplyWatermarks(tempFile.Name())
assert.Error(t, err, "No space for watermarking")
assert.Nil(t, checker)
})
t.Run("PositiveTestWithRandomDataAndWipe", func(t *testing.T) {
// Write the file full of random data
randomData := make([]byte, 15*1024*1024)
_, err := rand.Read(randomData)
if err != nil {
t.Fatalf("Failed to generate random data: %v", err)
}
err = os.WriteFile(tempFile.Name(), randomData, 0o600)
if err != nil {
t.Fatalf("Failed to write random data to file: %v", err)
}

// Apply watermarks and expect no error
checker, err := ApplyWatermarks(tempFile.Name())
if err != nil {
t.Fatalf("Error applying watermarks: %v", err)
}
// simulate wipe
assert.NoError(t, os.Truncate(tempFile.Name(), 0))
assert.NoError(t, os.Truncate(tempFile.Name(), 15*1024*1024))

err = checker()
if err != nil {
t.Errorf("Expected no error, got: %v", err)
}
})
}
Loading