Implement Phase 1: core framework, operational tooling, and runbook
Core packages: crypto (Argon2id/AES-256-GCM), config (TOML/viper), db (SQLite/migrations), barrier (encrypted storage), seal (state machine with rate-limited unseal), auth (MCIAS integration with token cache), policy (priority-based ACL engine), engine (interface + registry). Server: HTTPS with TLS 1.2+, REST API, auth/admin middleware, htmx web UI (init, unseal, login, dashboard pages). CLI: cobra/viper subcommands (server, init, status, snapshot) with env var override support (METACRYPT_ prefix). Operational tooling: Dockerfile (multi-stage, non-root), docker-compose, hardened systemd units (service + daily backup timer), install script, backup script with retention pruning, production config examples. Runbook covering installation, configuration, daily operations, backup/restore, monitoring, troubleshooting, and security procedures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
136
internal/seal/seal_test.go
Normal file
136
internal/seal/seal_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package seal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/barrier"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/crypto"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/db"
|
||||
)
|
||||
|
||||
func setupSeal(t *testing.T) (*Manager, func()) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
database, err := db.Open(filepath.Join(dir, "test.db"))
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
if err := db.Migrate(database); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
b := barrier.NewAESGCMBarrier(database)
|
||||
mgr := NewManager(database, b)
|
||||
return mgr, func() { database.Close() }
|
||||
}
|
||||
|
||||
func TestSealInitializeAndUnseal(t *testing.T) {
|
||||
mgr, cleanup := setupSeal(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := mgr.CheckInitialized(); err != nil {
|
||||
t.Fatalf("CheckInitialized: %v", err)
|
||||
}
|
||||
if mgr.State() != StateUninitialized {
|
||||
t.Fatalf("state: got %v, want Uninitialized", mgr.State())
|
||||
}
|
||||
|
||||
password := []byte("test-password-123")
|
||||
// Use fast params for testing.
|
||||
params := crypto.Argon2Params{Time: 1, Memory: 64 * 1024, Threads: 1}
|
||||
|
||||
if err := mgr.Initialize(context.Background(), password, params); err != nil {
|
||||
t.Fatalf("Initialize: %v", err)
|
||||
}
|
||||
if mgr.State() != StateUnsealed {
|
||||
t.Fatalf("state after init: got %v, want Unsealed", mgr.State())
|
||||
}
|
||||
|
||||
// Seal.
|
||||
if err := mgr.Seal(); err != nil {
|
||||
t.Fatalf("Seal: %v", err)
|
||||
}
|
||||
if mgr.State() != StateSealed {
|
||||
t.Fatalf("state after seal: got %v, want Sealed", mgr.State())
|
||||
}
|
||||
|
||||
// Unseal with correct password.
|
||||
if err := mgr.Unseal(password); err != nil {
|
||||
t.Fatalf("Unseal: %v", err)
|
||||
}
|
||||
if mgr.State() != StateUnsealed {
|
||||
t.Fatalf("state after unseal: got %v, want Unsealed", mgr.State())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSealWrongPassword(t *testing.T) {
|
||||
mgr, cleanup := setupSeal(t)
|
||||
defer cleanup()
|
||||
mgr.CheckInitialized()
|
||||
|
||||
params := crypto.Argon2Params{Time: 1, Memory: 64 * 1024, Threads: 1}
|
||||
mgr.Initialize(context.Background(), []byte("correct"), params)
|
||||
mgr.Seal()
|
||||
|
||||
err := mgr.Unseal([]byte("wrong"))
|
||||
if err != ErrInvalidPassword {
|
||||
t.Fatalf("expected ErrInvalidPassword, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSealDoubleInitialize(t *testing.T) {
|
||||
mgr, cleanup := setupSeal(t)
|
||||
defer cleanup()
|
||||
mgr.CheckInitialized()
|
||||
|
||||
params := crypto.Argon2Params{Time: 1, Memory: 64 * 1024, Threads: 1}
|
||||
mgr.Initialize(context.Background(), []byte("password"), params)
|
||||
|
||||
err := mgr.Initialize(context.Background(), []byte("password"), params)
|
||||
if err != ErrAlreadyInitialized {
|
||||
t.Fatalf("expected ErrAlreadyInitialized, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSealCheckInitializedPersists(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dbPath := filepath.Join(dir, "test.db")
|
||||
|
||||
// First: initialize.
|
||||
database, _ := db.Open(dbPath)
|
||||
db.Migrate(database)
|
||||
b := barrier.NewAESGCMBarrier(database)
|
||||
mgr := NewManager(database, b)
|
||||
mgr.CheckInitialized()
|
||||
params := crypto.Argon2Params{Time: 1, Memory: 64 * 1024, Threads: 1}
|
||||
mgr.Initialize(context.Background(), []byte("password"), params)
|
||||
database.Close()
|
||||
|
||||
// Second: reopen and check.
|
||||
database2, _ := db.Open(dbPath)
|
||||
defer database2.Close()
|
||||
b2 := barrier.NewAESGCMBarrier(database2)
|
||||
mgr2 := NewManager(database2, b2)
|
||||
mgr2.CheckInitialized()
|
||||
if mgr2.State() != StateSealed {
|
||||
t.Fatalf("state after reopen: got %v, want Sealed", mgr2.State())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSealStateString(t *testing.T) {
|
||||
tests := []struct {
|
||||
state ServiceState
|
||||
want string
|
||||
}{
|
||||
{StateUninitialized, "uninitialized"},
|
||||
{StateSealed, "sealed"},
|
||||
{StateInitializing, "initializing"},
|
||||
{StateUnsealed, "unsealed"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := tt.state.String(); got != tt.want {
|
||||
t.Errorf("State(%d).String() = %q, want %q", tt.state, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user