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:
2026-03-14 20:43:11 -07:00
commit 4ddd32b117
60 changed files with 4644 additions and 0 deletions

179
internal/engine/engine.go Normal file
View File

@@ -0,0 +1,179 @@
// Package engine defines the Engine interface and mount registry.
// Phase 1: interface and registry only, no concrete implementations.
package engine
import (
"context"
"errors"
"fmt"
"sync"
"git.wntrmute.dev/kyle/metacrypt/internal/barrier"
)
// EngineType identifies a cryptographic engine type.
type EngineType string
const (
EngineTypeCA EngineType = "ca"
EngineTypeSSHCA EngineType = "sshca"
EngineTypeTransit EngineType = "transit"
EngineTypeUser EngineType = "user"
)
var (
ErrMountExists = errors.New("engine: mount already exists")
ErrMountNotFound = errors.New("engine: mount not found")
ErrUnknownType = errors.New("engine: unknown engine type")
)
// Request is a request to an engine.
type Request struct {
Operation string
Path string
Data map[string]interface{}
}
// Response is a response from an engine.
type Response struct {
Data map[string]interface{}
}
// Engine is the interface that all cryptographic engines must implement.
type Engine interface {
// Type returns the engine type.
Type() EngineType
// Initialize sets up the engine for first use.
Initialize(ctx context.Context, b barrier.Barrier, mountPath string) error
// Unseal opens the engine using state from the barrier.
Unseal(ctx context.Context, b barrier.Barrier, mountPath string) error
// Seal closes the engine and zeroizes key material.
Seal() error
// HandleRequest processes a request.
HandleRequest(ctx context.Context, req *Request) (*Response, error)
}
// Factory creates a new engine instance of a given type.
type Factory func() Engine
// Mount represents a mounted engine instance.
type Mount struct {
Name string `json:"name"`
Type EngineType `json:"type"`
MountPath string `json:"mount_path"`
engine Engine
}
// Registry manages mounted engine instances.
type Registry struct {
mu sync.RWMutex
mounts map[string]*Mount
factories map[EngineType]Factory
barrier barrier.Barrier
}
// NewRegistry creates a new engine registry.
func NewRegistry(b barrier.Barrier) *Registry {
return &Registry{
mounts: make(map[string]*Mount),
factories: make(map[EngineType]Factory),
barrier: b,
}
}
// RegisterFactory registers a factory for the given engine type.
func (r *Registry) RegisterFactory(t EngineType, f Factory) {
r.mu.Lock()
defer r.mu.Unlock()
r.factories[t] = f
}
// Mount creates and initializes a new engine mount.
func (r *Registry) Mount(ctx context.Context, name string, engineType EngineType) error {
r.mu.Lock()
defer r.mu.Unlock()
if _, exists := r.mounts[name]; exists {
return ErrMountExists
}
factory, ok := r.factories[engineType]
if !ok {
return fmt.Errorf("%w: %s", ErrUnknownType, engineType)
}
eng := factory()
mountPath := fmt.Sprintf("engine/%s/%s/", engineType, name)
if err := eng.Initialize(ctx, r.barrier, mountPath); err != nil {
return fmt.Errorf("engine: initialize %q: %w", name, err)
}
r.mounts[name] = &Mount{
Name: name,
Type: engineType,
MountPath: mountPath,
engine: eng,
}
return nil
}
// Unmount removes and seals an engine mount.
func (r *Registry) Unmount(name string) error {
r.mu.Lock()
defer r.mu.Unlock()
mount, exists := r.mounts[name]
if !exists {
return ErrMountNotFound
}
if err := mount.engine.Seal(); err != nil {
return fmt.Errorf("engine: seal %q: %w", name, err)
}
delete(r.mounts, name)
return nil
}
// ListMounts returns all current mounts.
func (r *Registry) ListMounts() []Mount {
r.mu.RLock()
defer r.mu.RUnlock()
mounts := make([]Mount, 0, len(r.mounts))
for _, m := range r.mounts {
mounts = append(mounts, Mount{
Name: m.Name,
Type: m.Type,
MountPath: m.MountPath,
})
}
return mounts
}
// HandleRequest routes a request to the appropriate engine.
func (r *Registry) HandleRequest(ctx context.Context, mountName string, req *Request) (*Response, error) {
r.mu.RLock()
mount, exists := r.mounts[mountName]
r.mu.RUnlock()
if !exists {
return nil, ErrMountNotFound
}
return mount.engine.HandleRequest(ctx, req)
}
// SealAll seals all mounted engines.
func (r *Registry) SealAll() error {
r.mu.Lock()
defer r.mu.Unlock()
for name, mount := range r.mounts {
if err := mount.engine.Seal(); err != nil {
return fmt.Errorf("engine: seal %q: %w", name, err)
}
}
return nil
}

View File

@@ -0,0 +1,120 @@
package engine
import (
"context"
"testing"
"git.wntrmute.dev/kyle/metacrypt/internal/barrier"
)
// mockEngine implements Engine for testing.
type mockEngine struct {
engineType EngineType
initialized bool
unsealed bool
}
func (m *mockEngine) Type() EngineType { return m.engineType }
func (m *mockEngine) Initialize(_ context.Context, _ barrier.Barrier, _ string) error { m.initialized = true; return nil }
func (m *mockEngine) Unseal(_ context.Context, _ barrier.Barrier, _ string) error { m.unsealed = true; return nil }
func (m *mockEngine) Seal() error { m.unsealed = false; return nil }
func (m *mockEngine) HandleRequest(_ context.Context, _ *Request) (*Response, error) {
return &Response{Data: map[string]interface{}{"ok": true}}, nil
}
type mockBarrier struct{}
func (m *mockBarrier) Unseal(_ []byte) error { return nil }
func (m *mockBarrier) Seal() error { return nil }
func (m *mockBarrier) IsSealed() bool { return false }
func (m *mockBarrier) Get(_ context.Context, _ string) ([]byte, error) { return nil, barrier.ErrNotFound }
func (m *mockBarrier) Put(_ context.Context, _ string, _ []byte) error { return nil }
func (m *mockBarrier) Delete(_ context.Context, _ string) error { return nil }
func (m *mockBarrier) List(_ context.Context, _ string) ([]string, error) { return nil, nil }
func TestRegistryMountUnmount(t *testing.T) {
reg := NewRegistry(&mockBarrier{})
reg.RegisterFactory(EngineTypeTransit, func() Engine {
return &mockEngine{engineType: EngineTypeTransit}
})
ctx := context.Background()
if err := reg.Mount(ctx, "default", EngineTypeTransit); err != nil {
t.Fatalf("Mount: %v", err)
}
mounts := reg.ListMounts()
if len(mounts) != 1 {
t.Fatalf("ListMounts: got %d, want 1", len(mounts))
}
if mounts[0].Name != "default" {
t.Errorf("mount name: got %q, want %q", mounts[0].Name, "default")
}
// Duplicate mount should fail.
if err := reg.Mount(ctx, "default", EngineTypeTransit); err != ErrMountExists {
t.Fatalf("expected ErrMountExists, got: %v", err)
}
if err := reg.Unmount("default"); err != nil {
t.Fatalf("Unmount: %v", err)
}
mounts = reg.ListMounts()
if len(mounts) != 0 {
t.Fatalf("after unmount: got %d mounts", len(mounts))
}
}
func TestRegistryUnmountNotFound(t *testing.T) {
reg := NewRegistry(&mockBarrier{})
if err := reg.Unmount("nonexistent"); err != ErrMountNotFound {
t.Fatalf("expected ErrMountNotFound, got: %v", err)
}
}
func TestRegistryUnknownType(t *testing.T) {
reg := NewRegistry(&mockBarrier{})
err := reg.Mount(context.Background(), "test", EngineTypeTransit)
if err == nil {
t.Fatal("expected error for unknown engine type")
}
}
func TestRegistryHandleRequest(t *testing.T) {
reg := NewRegistry(&mockBarrier{})
reg.RegisterFactory(EngineTypeTransit, func() Engine {
return &mockEngine{engineType: EngineTypeTransit}
})
ctx := context.Background()
reg.Mount(ctx, "test", EngineTypeTransit)
resp, err := reg.HandleRequest(ctx, "test", &Request{Operation: "encrypt"})
if err != nil {
t.Fatalf("HandleRequest: %v", err)
}
if resp.Data["ok"] != true {
t.Error("expected ok=true in response")
}
_, err = reg.HandleRequest(ctx, "nonexistent", &Request{})
if err != ErrMountNotFound {
t.Fatalf("expected ErrMountNotFound, got: %v", err)
}
}
func TestRegistrySealAll(t *testing.T) {
reg := NewRegistry(&mockBarrier{})
reg.RegisterFactory(EngineTypeTransit, func() Engine {
return &mockEngine{engineType: EngineTypeTransit}
})
ctx := context.Background()
reg.Mount(ctx, "eng1", EngineTypeTransit)
reg.Mount(ctx, "eng2", EngineTypeTransit)
if err := reg.SealAll(); err != nil {
t.Fatalf("SealAll: %v", err)
}
}