Merge transit engine branch, resolve conflicts in shared files
This commit is contained in:
160
cmd/metacrypt/migrate_barrier.go
Normal file
160
cmd/metacrypt/migrate_barrier.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user