0.27.0 — Infisical hardening: runtime refresh, audit logging, CLI verify/audit, startup verification, SSH host key verification, interface consolidation
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

This commit is contained in:
2026-08-05 23:51:01 +02:00
parent e3449b24c1
commit c9d506b0f8
19 changed files with 1081 additions and 75 deletions

View File

@@ -24,6 +24,7 @@ import (
"time"
"github.com/dtoro/oikos/internal/config"
"github.com/dtoro/oikos/internal/actuator"
"github.com/dtoro/oikos/internal/db"
"github.com/dtoro/oikos/internal/httpapi/gen"
mcphandler "github.com/dtoro/oikos/internal/mcp"
@@ -53,22 +54,12 @@ type actor struct {
type Server struct {
pool *db.Pool
cfg config.Config
secretsManager secretsBackend
secretsManager secrets.Backend
sseBroker *sseBroker
sseSubs map[*sseSubscriber]struct{}
sseMu sync.Mutex
}
// secretsBackend is a minimal interface for secrets operations used by the
// HTTP API (enrollment key storage, listing, retrieval). Compatible with
// internal/secrets.Backend. If Infisical is configured, a real backend is
// wired in; otherwise the field stays nil and all guarded paths are no-ops.
type secretsBackend interface {
Get(ctx context.Context, key string) (string, error)
Set(ctx context.Context, key string, value string) error
List(ctx context.Context) ([]string, error)
}
// NewHandler builds the full HTTP handler: /healthz (unauthenticated,
// SG18) + the OpenAPI surface under /api/v1 behind bearer auth.
//
@@ -84,7 +75,7 @@ func NewHandler(ctx context.Context, pool *db.Pool, cfg config.Config) http.Hand
sseSubs: make(map[*sseSubscriber]struct{}),
}
// Wire Infisical backend when configured.
// Wire secrets backend: Infisical primary with SOPS DR fallback.
if cfg.InfisicalSiteURL != "" {
infCfg := secrets.InfisicalConfig{
SiteURL: cfg.InfisicalSiteURL,
@@ -97,8 +88,24 @@ func NewHandler(ctx context.Context, pool *db.Pool, cfg config.Config) http.Hand
if infCfg.Env == "" {
infCfg.Env = "dev"
}
s.secretsManager = secrets.NewInfisicalBackend(infCfg)
slog.Info("secrets backend wired", "backend", "infisical", "site", cfg.InfisicalSiteURL)
primary := secrets.NewInfisicalBackend(infCfg)
var fallback secrets.Backend
if cfg.SecretsDir != "" {
fallback = secrets.NewSOPSBackend(cfg.SecretsDir)
}
s.secretsManager = secrets.NewManager(primary, fallback)
slog.Info("secrets backend wired", "backend", "infisical+sops", "site", cfg.InfisicalSiteURL)
// Start background secret refresh loop.
if mgr, ok := s.secretsManager.(*secrets.Manager); ok {
mgr.StartRefreshLoop(ctx)
}
// Pre-load SSH host keys from Infisical for host verification.
if hosts := actuator.ResolveSSHHosts(ctx, pool); len(hosts) > 0 {
hkSrc := actuator.NewInfisicalHostKeySource(s.secretsManager)
actuator.LoadHostKeys(ctx, hosts, hkSrc)
}
}
// Start background SSE listener, tied to ctx for clean shutdown.