Implement CA/PKI engine with two-tier X.509 certificate issuance

Add the first concrete engine implementation: a CA (PKI) engine that generates
a self-signed root CA at mount time, issues scoped intermediate CAs ("issuers"),
and signs leaf certificates using configurable profiles (server, client, peer).

Engine framework updates:
- Add CallerInfo struct for auth context in engine requests
- Add config parameter to Engine.Initialize for mount-time configuration
- Export Mount.Engine field; add GetEngine/GetMount on Registry

CA engine (internal/engine/ca/):
- Two-tier PKI: root CA → issuers → leaf certificates
- 10 operations: get-root, get-chain, get-issuer, create/delete/list issuers,
  issue, get-cert, list-certs, renew
- Certificate profiles with user-overridable TTL, key usages, and key algorithm
- Private keys never stored in barrier; zeroized from memory on seal
- Supports ECDSA, RSA, and Ed25519 key types via goutils/certlib/certgen

Server routes:
- Wire up engine mount/request handlers (replace Phase 1 stubs)
- Add public PKI routes (/v1/pki/{mount}/ca, /ca/chain, /issuer/{name})
  for unauthenticated TLS trust bootstrapping

Also includes: ARCHITECTURE.md, deploy config updates, operational tooling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 21:57:52 -07:00
parent 4ddd32b117
commit 8f77050a84
26 changed files with 2980 additions and 129 deletions

View File

@@ -27,11 +27,19 @@ var (
ErrUnknownType = errors.New("engine: unknown engine type")
)
// CallerInfo carries authentication context into engines.
type CallerInfo struct {
Username string
Roles []string
IsAdmin bool
}
// Request is a request to an engine.
type Request struct {
Operation string
Path string
Data map[string]interface{}
Operation string
Path string
Data map[string]interface{}
CallerInfo *CallerInfo
}
// Response is a response from an engine.
@@ -44,7 +52,7 @@ 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
Initialize(ctx context.Context, b barrier.Barrier, mountPath string, config map[string]interface{}) 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.
@@ -58,10 +66,10 @@ 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
Name string `json:"name"`
Type EngineType `json:"type"`
MountPath string `json:"mount_path"`
Engine Engine `json:"-"`
}
// Registry manages mounted engine instances.
@@ -89,7 +97,7 @@ func (r *Registry) RegisterFactory(t EngineType, f Factory) {
}
// Mount creates and initializes a new engine mount.
func (r *Registry) Mount(ctx context.Context, name string, engineType EngineType) error {
func (r *Registry) Mount(ctx context.Context, name string, engineType EngineType, config map[string]interface{}) error {
r.mu.Lock()
defer r.mu.Unlock()
@@ -105,7 +113,7 @@ func (r *Registry) Mount(ctx context.Context, name string, engineType EngineType
eng := factory()
mountPath := fmt.Sprintf("engine/%s/%s/", engineType, name)
if err := eng.Initialize(ctx, r.barrier, mountPath); err != nil {
if err := eng.Initialize(ctx, r.barrier, mountPath, config); err != nil {
return fmt.Errorf("engine: initialize %q: %w", name, err)
}
@@ -113,11 +121,35 @@ func (r *Registry) Mount(ctx context.Context, name string, engineType EngineType
Name: name,
Type: engineType,
MountPath: mountPath,
engine: eng,
Engine: eng,
}
return nil
}
// GetEngine returns the engine for the given mount name.
func (r *Registry) GetEngine(name string) (Engine, error) {
r.mu.RLock()
defer r.mu.RUnlock()
mount, exists := r.mounts[name]
if !exists {
return nil, ErrMountNotFound
}
return mount.Engine, nil
}
// GetMount returns the mount for the given name.
func (r *Registry) GetMount(name string) (*Mount, error) {
r.mu.RLock()
defer r.mu.RUnlock()
mount, exists := r.mounts[name]
if !exists {
return nil, ErrMountNotFound
}
return mount, nil
}
// Unmount removes and seals an engine mount.
func (r *Registry) Unmount(name string) error {
r.mu.Lock()
@@ -128,7 +160,7 @@ func (r *Registry) Unmount(name string) error {
return ErrMountNotFound
}
if err := mount.engine.Seal(); err != nil {
if err := mount.Engine.Seal(); err != nil {
return fmt.Errorf("engine: seal %q: %w", name, err)
}
@@ -162,7 +194,7 @@ func (r *Registry) HandleRequest(ctx context.Context, mountName string, req *Req
return nil, ErrMountNotFound
}
return mount.engine.HandleRequest(ctx, req)
return mount.Engine.HandleRequest(ctx, req)
}
// SealAll seals all mounted engines.
@@ -171,7 +203,7 @@ func (r *Registry) SealAll() error {
defer r.mu.Unlock()
for name, mount := range r.mounts {
if err := mount.engine.Seal(); err != nil {
if err := mount.Engine.Seal(); err != nil {
return fmt.Errorf("engine: seal %q: %w", name, err)
}
}