Merge transit engine branch, resolve conflicts in shared files

This commit is contained in:
2026-03-16 19:50:47 -07:00
14 changed files with 7969 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
package main
import (
"context"
"database/sql"
"fmt"
"os"
"syscall"
"github.com/spf13/cobra"
"golang.org/x/term"
"git.wntrmute.dev/kyle/metacrypt/internal/barrier"
"git.wntrmute.dev/kyle/metacrypt/internal/config"
"git.wntrmute.dev/kyle/metacrypt/internal/crypto"
"git.wntrmute.dev/kyle/metacrypt/internal/db"
)
var migrateBarrierCmd = &cobra.Command{
Use: "migrate-barrier",
Short: "Migrate barrier entries from v1 (MEK) to v2 (per-engine DEKs)",
Long: `Converts all v1 barrier entries to v2 format with per-engine data
encryption keys (DEKs). Creates a "system" DEK for non-engine data and
per-engine DEKs for each engine mount found in the barrier.
After migration, each engine's data is encrypted with its own DEK rather
than the MEK directly, limiting blast radius if a single key is compromised.
The MEK only wraps the DEKs.
Entries already in v2 format are skipped. Run while the server is stopped
to avoid concurrent access. Requires the unseal password.`,
RunE: runMigrateBarrier,
}
var migrateBarrierDryRun bool
func init() {
migrateBarrierCmd.Flags().BoolVar(&migrateBarrierDryRun, "dry-run", false, "report what would be migrated without writing")
rootCmd.AddCommand(migrateBarrierCmd)
}
func runMigrateBarrier(cmd *cobra.Command, args []string) error {
configPath := cfgFile
if configPath == "" {
configPath = "/srv/metacrypt/metacrypt.toml"
}
cfg, err := config.Load(configPath)
if err != nil {
return err
}
database, err := db.Open(cfg.Database.Path)
if err != nil {
return err
}
defer func() { _ = database.Close() }()
// Apply any pending schema migrations (creates barrier_keys table).
if err := db.Migrate(database); err != nil {
return fmt.Errorf("schema migration: %w", err)
}
// Read unseal password.
fmt.Fprint(os.Stderr, "Unseal password: ")
passwordBytes, err := term.ReadPassword(int(syscall.Stdin))
fmt.Fprintln(os.Stderr)
if err != nil {
return fmt.Errorf("read password: %w", err)
}
defer crypto.Zeroize(passwordBytes)
// Load seal config and derive MEK.
mek, err := deriveMEK(database, passwordBytes)
if err != nil {
return err
}
defer crypto.Zeroize(mek)
if migrateBarrierDryRun {
return migrateBarrierDryRunReport(database, mek)
}
// Unseal the barrier with the MEK (loads existing DEKs).
b := barrier.NewAESGCMBarrier(database)
if err := b.Unseal(mek); err != nil {
return fmt.Errorf("unseal barrier: %w", err)
}
defer func() { _ = b.Seal() }()
ctx := context.Background()
migrated, err := b.MigrateToV2(ctx)
if err != nil {
return fmt.Errorf("migration failed: %w", err)
}
if migrated == 0 {
fmt.Println("Nothing to migrate — all entries already use v2 format.")
} else {
fmt.Printf("Migrated %d entries to v2 format with per-engine DEKs.\n", migrated)
}
// Show key summary.
keys, err := b.ListKeys(ctx)
if err != nil {
return fmt.Errorf("list keys: %w", err)
}
if len(keys) > 0 {
fmt.Printf("\nBarrier keys (%d):\n", len(keys))
for _, k := range keys {
fmt.Printf(" %-30s version=%d created=%s\n", k.KeyID, k.Version, k.CreatedAt)
}
}
return nil
}
func migrateBarrierDryRunReport(database *sql.DB, mek []byte) error {
ctx := context.Background()
rows, err := database.QueryContext(ctx, "SELECT path, value FROM barrier_entries")
if err != nil {
return fmt.Errorf("query entries: %w", err)
}
defer func() { _ = rows.Close() }()
var v1Count, v2Count, total int
var v1Paths []string
for rows.Next() {
var path string
var value []byte
if err := rows.Scan(&path, &value); err != nil {
return fmt.Errorf("scan: %w", err)
}
total++
if len(value) > 0 && value[0] == crypto.BarrierVersionV2 {
v2Count++
} else {
v1Count++
v1Paths = append(v1Paths, path)
}
}
if err := rows.Err(); err != nil {
return fmt.Errorf("iterate: %w", err)
}
fmt.Printf("Total entries: %d\n", total)
fmt.Printf("Already v2: %d\n", v2Count)
fmt.Printf("Need migration: %d\n", v1Count)
if len(v1Paths) > 0 {
fmt.Println("\nEntries that would be migrated:")
for _, p := range v1Paths {
fmt.Printf(" %s\n", p)
}
} else {
fmt.Println("\nNothing to migrate.")
}
return nil
}

View File

@@ -17,6 +17,7 @@ import (
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
"git.wntrmute.dev/kyle/metacrypt/internal/engine/ca"
"git.wntrmute.dev/kyle/metacrypt/internal/engine/sshca"
"git.wntrmute.dev/kyle/metacrypt/internal/engine/transit"
"git.wntrmute.dev/kyle/metacrypt/internal/grpcserver"
"git.wntrmute.dev/kyle/metacrypt/internal/policy"
"git.wntrmute.dev/kyle/metacrypt/internal/seal"
@@ -76,6 +77,7 @@ func runServer(cmd *cobra.Command, args []string) error {
engineRegistry := engine.NewRegistry(b, logger)
engineRegistry.RegisterFactory(engine.EngineTypeCA, ca.NewCAEngine)
engineRegistry.RegisterFactory(engine.EngineTypeSSHCA, sshca.NewSSHCAEngine)
engineRegistry.RegisterFactory(engine.EngineTypeTransit, transit.NewTransitEngine)
srv := server.New(cfg, sealMgr, authenticator, policyEngine, engineRegistry, logger, version)
grpcSrv := grpcserver.New(cfg, sealMgr, authenticator, policyEngine, engineRegistry, logger)