Files
zeekay b4a26d51a8 fix: use published kms SDK module path, remove local replace
Replace the local filesystem replace directive with the properly
published module at github.com/hanzoai/kms/sdk/go v1.0.0. The
local path /Users/z/work/hanzo/kms/sdk/go doesn't exist in CI.
2026-03-02 03:16:33 -07:00

143 lines
3.6 KiB
Go

// Copyright (C) 2020-2026, Hanzo AI Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package kms
import (
"log/slog"
"github.com/hanzoai/base/core"
"github.com/hanzoai/base/tools/hook"
sdk "github.com/hanzoai/kms/sdk/go"
)
// MustRegister registers the KMS plugin with a Base app and panics on error.
//
// Usage:
//
// kms.MustRegister(app, kms.Config{
// Nodes: []string{"https://kms-mpc-0:9651", "https://kms-mpc-1:9651", "https://kms-mpc-2:9651"},
// OrgSlug: "my-org",
// Threshold: 2,
// EncryptedCollections: map[string][]string{
// "credentials": {"api_key", "api_secret"},
// },
// FHESearchable: map[string][]string{
// "credentials": {"name"},
// },
// })
func MustRegister(app core.App, config Config) {
if err := Register(app, config); err != nil {
panic(err)
}
}
// Register registers the KMS plugin with a Base app. The plugin provides:
// - Zero-knowledge encrypted secret management via REST API
// - Transparent field-level encryption on configured collections
// - FHE-encrypted indexes for querying encrypted fields without decryption
func Register(app core.App, config Config) error {
if !config.Enabled {
return nil
}
if err := config.validate(); err != nil {
return err
}
client, err := sdk.NewClient(sdk.Config{
Nodes: config.Nodes,
OrgSlug: config.OrgSlug,
Threshold: config.Threshold,
})
if err != nil {
return err
}
p := &plugin{
app: app,
config: config,
client: client,
logger: slog.Default().With("component", "kms"),
}
// Build lookup maps for fast hook checks.
p.encFieldSet = make(map[string]map[string]struct{}, len(config.EncryptedCollections))
for col, fields := range config.EncryptedCollections {
m := make(map[string]struct{}, len(fields))
for _, f := range fields {
m[f] = struct{}{}
}
p.encFieldSet[col] = m
}
p.fheFieldSet = make(map[string]map[string]struct{}, len(config.FHESearchable))
for col, fields := range config.FHESearchable {
m := make(map[string]struct{}, len(fields))
for _, f := range fields {
m[f] = struct{}{}
}
p.fheFieldSet[col] = m
}
// Bootstrap: auto-unlock if configured.
app.OnBootstrap().Bind(&hook.Handler[*core.BootstrapEvent]{
Id: "__kmsBootstrap__",
Func: func(e *core.BootstrapEvent) error {
if err := e.Next(); err != nil {
return err
}
if config.AutoUnlock {
passphrase := config.resolvePassphrase()
if passphrase != "" {
if unlockErr := client.Unlock(passphrase); unlockErr != nil {
p.logger.Warn("kms: auto-unlock failed", "error", unlockErr)
} else {
p.logger.Info("kms: auto-unlock succeeded")
}
}
}
return nil
},
})
// Serve: register routes and record hooks.
app.OnServe().Bind(&hook.Handler[*core.ServeEvent]{
Id: "__kmsServe__",
Func: func(e *core.ServeEvent) error {
// Store reference for programmatic access.
app.Store().Set("kms", p)
p.registerRoutes(e.Router)
p.registerHooks()
return e.Next()
},
})
// Terminate: lock CEK on shutdown.
app.OnTerminate().Bind(&hook.Handler[*core.TerminateEvent]{
Id: "__kmsTerminate__",
Func: func(e *core.TerminateEvent) error {
p.logger.Info("kms: locking CEK on shutdown")
client.Lock()
return e.Next()
},
})
return nil
}
type plugin struct {
app core.App
config Config
client *sdk.Client
logger *slog.Logger
// Pre-computed field sets for O(1) lookup in hooks.
encFieldSet map[string]map[string]struct{} // collection -> field -> struct{}
fheFieldSet map[string]map[string]struct{} // collection -> field -> struct{}
}