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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user