Merge pull request #8 from Snider/feat-rootfs-passthrough-storage

feat: Implement rootfs passthrough storage
This commit is contained in:
Snider 2025-10-31 00:59:03 +00:00 committed by GitHub
commit cf48c6d623
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 159 additions and 0 deletions

View file

@ -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
View 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
View 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
View 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
View 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)
}