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:
93
cmd/metacrypt/init.go
Normal file
93
cmd/metacrypt/init.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/seal"
|
||||
)
|
||||
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Interactive first-time setup",
|
||||
Long: "Initialize Metacrypt with a seal password. This must be run before the server can be used.",
|
||||
RunE: runInit,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(initCmd)
|
||||
}
|
||||
|
||||
func runInit(cmd *cobra.Command, args []string) error {
|
||||
configPath := cfgFile
|
||||
if configPath == "" {
|
||||
configPath = "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 database.Close()
|
||||
|
||||
if err := db.Migrate(database); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := barrier.NewAESGCMBarrier(database)
|
||||
sealMgr := seal.NewManager(database, b)
|
||||
if err := sealMgr.CheckInitialized(); err != nil {
|
||||
return err
|
||||
}
|
||||
if sealMgr.State() != seal.StateUninitialized {
|
||||
return fmt.Errorf("already initialized")
|
||||
}
|
||||
|
||||
fmt.Print("Enter seal password: ")
|
||||
pw1, err := term.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading password: %w", err)
|
||||
}
|
||||
|
||||
fmt.Print("Confirm seal password: ")
|
||||
pw2, err := term.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading password: %w", err)
|
||||
}
|
||||
|
||||
if !crypto.ConstantTimeEqual(pw1, pw2) {
|
||||
return fmt.Errorf("passwords do not match")
|
||||
}
|
||||
|
||||
params := crypto.Argon2Params{
|
||||
Time: cfg.Seal.Argon2Time,
|
||||
Memory: cfg.Seal.Argon2Memory,
|
||||
Threads: cfg.Seal.Argon2Threads,
|
||||
}
|
||||
|
||||
fmt.Println("Initializing (this may take a moment)...")
|
||||
if err := sealMgr.Initialize(context.Background(), pw1, params); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
crypto.Zeroize(pw1)
|
||||
crypto.Zeroize(pw2)
|
||||
|
||||
fmt.Println("Metacrypt initialized and unsealed successfully.")
|
||||
return nil
|
||||
}
|
||||
14
cmd/metacrypt/main.go
Normal file
14
cmd/metacrypt/main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Metacrypt is a cryptographic service for the Metacircular platform.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
33
cmd/metacrypt/root.go
Normal file
33
cmd/metacrypt/root.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "metacrypt",
|
||||
Short: "Metacrypt cryptographic service",
|
||||
Long: "Metacrypt is a cryptographic service for the Metacircular platform.",
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default metacrypt.toml)")
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
viper.SetConfigName("metacrypt")
|
||||
viper.SetConfigType("toml")
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath("/etc/metacrypt")
|
||||
}
|
||||
viper.AutomaticEnv()
|
||||
viper.SetEnvPrefix("METACRYPT")
|
||||
viper.ReadInConfig()
|
||||
}
|
||||
90
cmd/metacrypt/server.go
Normal file
90
cmd/metacrypt/server.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
mcias "git.wntrmute.dev/kyle/mcias/clients/go"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/auth"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/barrier"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/config"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/db"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/engine"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/policy"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/seal"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/server"
|
||||
)
|
||||
|
||||
var serverCmd = &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "Start the Metacrypt server",
|
||||
Long: "Start the Metacrypt HTTPS server. The service starts in sealed state.",
|
||||
RunE: runServer,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(serverCmd)
|
||||
}
|
||||
|
||||
func runServer(cmd *cobra.Command, args []string) error {
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
|
||||
configPath := cfgFile
|
||||
if configPath == "" {
|
||||
configPath = "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 database.Close()
|
||||
|
||||
if err := db.Migrate(database); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := barrier.NewAESGCMBarrier(database)
|
||||
sealMgr := seal.NewManager(database, b)
|
||||
|
||||
if err := sealMgr.CheckInitialized(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mcClient, err := mcias.New(cfg.MCIAS.ServerURL, mcias.Options{
|
||||
CACertPath: cfg.MCIAS.CACert,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authenticator := auth.NewAuthenticator(mcClient)
|
||||
policyEngine := policy.NewEngine(b)
|
||||
engineRegistry := engine.NewRegistry(b)
|
||||
|
||||
srv := server.New(cfg, sealMgr, authenticator, policyEngine, engineRegistry, logger)
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
go func() {
|
||||
if err := srv.Start(); err != nil {
|
||||
logger.Error("server error", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
logger.Info("shutting down")
|
||||
return srv.Shutdown(context.Background())
|
||||
}
|
||||
58
cmd/metacrypt/snapshot.go
Normal file
58
cmd/metacrypt/snapshot.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/config"
|
||||
"git.wntrmute.dev/kyle/metacrypt/internal/db"
|
||||
)
|
||||
|
||||
var snapshotCmd = &cobra.Command{
|
||||
Use: "snapshot",
|
||||
Short: "Create a database snapshot",
|
||||
Long: "Create a backup of the Metacrypt database using SQLite's VACUUM INTO.",
|
||||
RunE: runSnapshot,
|
||||
}
|
||||
|
||||
var snapshotOutput string
|
||||
|
||||
func init() {
|
||||
snapshotCmd.Flags().StringVarP(&snapshotOutput, "output", "o", "backup.db", "output file path")
|
||||
rootCmd.AddCommand(snapshotCmd)
|
||||
}
|
||||
|
||||
func runSnapshot(cmd *cobra.Command, args []string) error {
|
||||
configPath := cfgFile
|
||||
if configPath == "" {
|
||||
configPath = "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 database.Close()
|
||||
|
||||
if err := sqliteBackup(database, snapshotOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Snapshot saved to %s\n", snapshotOutput)
|
||||
return nil
|
||||
}
|
||||
|
||||
func sqliteBackup(srcDB *sql.DB, dstPath string) error {
|
||||
_, err := srcDB.Exec("VACUUM INTO ?", dstPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("snapshot: %w", err)
|
||||
}
|
||||
return os.Chmod(dstPath, 0600)
|
||||
}
|
||||
66
cmd/metacrypt/status.go
Normal file
66
cmd/metacrypt/status.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Check service seal state",
|
||||
Long: "Query a running Metacrypt server for its current seal state.",
|
||||
RunE: runStatus,
|
||||
}
|
||||
|
||||
var (
|
||||
statusAddr string
|
||||
statusCACert string
|
||||
)
|
||||
|
||||
func init() {
|
||||
statusCmd.Flags().StringVar(&statusAddr, "addr", "", "server address (e.g., https://localhost:8443)")
|
||||
statusCmd.Flags().StringVar(&statusCACert, "ca-cert", "", "path to CA certificate for TLS verification")
|
||||
statusCmd.MarkFlagRequired("addr")
|
||||
rootCmd.AddCommand(statusCmd)
|
||||
}
|
||||
|
||||
func runStatus(cmd *cobra.Command, args []string) error {
|
||||
tlsCfg := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
|
||||
if statusCACert != "" {
|
||||
pem, err := os.ReadFile(statusCACert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read CA cert: %w", err)
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(pem) {
|
||||
return fmt.Errorf("no valid certs in CA file")
|
||||
}
|
||||
tlsCfg.RootCAs = pool
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: tlsCfg},
|
||||
}
|
||||
|
||||
resp, err := client.Get(statusAddr + "/v1/status")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var status struct {
|
||||
State string `json:"state"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
|
||||
return fmt.Errorf("decode response: %w", err)
|
||||
}
|
||||
fmt.Printf("State: %s\n", status.State)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user