Merge pull request #8 from Snider/feat-rootfs-passthrough-storage
feat: Implement rootfs passthrough storage
This commit is contained in:
commit
cf48c6d623
5 changed files with 159 additions and 0 deletions
|
|
@ -2,6 +2,7 @@ package crypt
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -46,3 +47,22 @@ func TestFletcher64(t *testing.T) {
|
|||
assert.Equal(t, uint64(0xc8c72b276463c8c6), Fletcher64("abcdef"))
|
||||
assert.Equal(t, uint64(0x312e2b28cccac8c6), Fletcher64("abcdefgh"))
|
||||
}
|
||||
|
||||
func TestRootFS(t *testing.T) {
|
||||
tempDir, err := os.MkdirTemp("", "enchantrix-crypt-test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
key := make([]byte, 32)
|
||||
for i := range key {
|
||||
key[i] = 1
|
||||
}
|
||||
|
||||
fs := NewRootFS(tempDir, key)
|
||||
err = fs.Write("test.txt", []byte("hello"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
data, err := fs.Read("test.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("hello"), data)
|
||||
}
|
||||
|
|
|
|||
11
rootfs.go
Normal file
11
rootfs.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package crypt
|
||||
|
||||
import "github.com/Snider/Enchantrix/rootfs"
|
||||
|
||||
// Storage is an alias for the rootfs.Storage interface.
|
||||
type Storage = rootfs.Storage
|
||||
|
||||
// NewRootFS creates a new encrypted passthrough storage system.
|
||||
func NewRootFS(root string, key []byte) Storage {
|
||||
return rootfs.NewLocalStorage(root, key)
|
||||
}
|
||||
71
rootfs/local.go
Normal file
71
rootfs/local.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package rootfs
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Snider/Enchantrix/chachapoly"
|
||||
)
|
||||
|
||||
// LocalStorage provides a passthrough storage system that encrypts data at rest.
|
||||
type LocalStorage struct {
|
||||
root string
|
||||
key []byte
|
||||
filePerm fs.FileMode
|
||||
dirPerm fs.FileMode
|
||||
}
|
||||
|
||||
// NewLocalStorage creates a new LocalStorage.
|
||||
func NewLocalStorage(root string, key []byte) *LocalStorage {
|
||||
return &LocalStorage{
|
||||
root: root,
|
||||
key: key,
|
||||
filePerm: 0644,
|
||||
dirPerm: 0755,
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads and decrypts the data for the given key.
|
||||
func (s *LocalStorage) Read(key string) ([]byte, error) {
|
||||
path := filepath.Join(s.root, key)
|
||||
ciphertext, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return chachapoly.Decrypt(ciphertext, s.key)
|
||||
}
|
||||
|
||||
// Write encrypts and writes the data for the given key.
|
||||
func (s *LocalStorage) Write(key string, data []byte) error {
|
||||
ciphertext, err := chachapoly.Encrypt(data, s.key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(s.root, key)
|
||||
if err := os.MkdirAll(filepath.Dir(path), s.dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, ciphertext, s.filePerm)
|
||||
}
|
||||
|
||||
// Delete deletes the data for the given key.
|
||||
func (s *LocalStorage) Delete(key string) error {
|
||||
path := filepath.Join(s.root, key)
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
// List lists the keys in the storage.
|
||||
func (s *LocalStorage) List(prefix string) ([]fs.FileInfo, error) {
|
||||
var files []fs.FileInfo
|
||||
err := filepath.Walk(filepath.Join(s.root, prefix), func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
files = append(files, info)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return files, err
|
||||
}
|
||||
42
rootfs/local_test.go
Normal file
42
rootfs/local_test.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package rootfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLocalStorage(t *testing.T) {
|
||||
// Create a temporary directory for testing.
|
||||
tempDir, err := os.MkdirTemp("", "enchantrix-test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create a new LocalStorage instance.
|
||||
key := make([]byte, 32)
|
||||
for i := range key {
|
||||
key[i] = 1
|
||||
}
|
||||
storage := NewLocalStorage(tempDir, key)
|
||||
|
||||
// Test Write and Read.
|
||||
err = storage.Write("test.txt", []byte("hello"))
|
||||
assert.NoError(t, err)
|
||||
data, err := storage.Read("test.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("hello"), data)
|
||||
|
||||
// Test List.
|
||||
files, err := storage.List("")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, files, 1)
|
||||
assert.Equal(t, "test.txt", files[0].Name())
|
||||
|
||||
// Test Delete.
|
||||
err = storage.Delete("test.txt")
|
||||
assert.NoError(t, err)
|
||||
_, err = os.Stat(filepath.Join(tempDir, "test.txt"))
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
15
rootfs/storage.go
Normal file
15
rootfs/storage.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package rootfs
|
||||
|
||||
import "io/fs"
|
||||
|
||||
// Storage defines the interface for a passthrough storage system.
|
||||
type Storage interface {
|
||||
// Read reads the data for the given key.
|
||||
Read(key string) ([]byte, error)
|
||||
// Write writes the data for the given key.
|
||||
Write(key string, data []byte) error
|
||||
// Delete deletes the data for the given key.
|
||||
Delete(key string) error
|
||||
// List lists the keys in the storage.
|
||||
List(prefix string) ([]fs.FileInfo, error)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue