feat: wire Infisical secret store into API server and MCP tools
- Wire secretsManager in NewHandler() — instantiate InfisicalBackend when OIKOS_INFISICAL_SITE_URL is set (previously always nil) - Add get_secret, list_secrets, set_secret MCP tools with nil-backend graceful degradation - Add oikos secret get|set|list CLI subcommands for Infisical - Fix Set() bug: create-before-update so new keys are created; add Type: "shared" to Update so it finds the right secret; disable SDK cache so Get returns fresh data after Set - Clean enrollment response: remove fake infisical_client_id/ infisical_client_secret stubs, store age key in Infisical for real
This commit is contained in:
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/dtoro/oikos/internal/httpapi/gen"
|
||||
mcphandler "github.com/dtoro/oikos/internal/mcp"
|
||||
"github.com/dtoro/oikos/internal/safego"
|
||||
"github.com/dtoro/oikos/internal/secrets"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
@@ -59,8 +60,11 @@ type Server struct {
|
||||
}
|
||||
|
||||
// secretsBackend is a minimal interface for secrets operations used by the
|
||||
// HTTP API (enrollment key storage, listing). Compatible with internal/secrets.
|
||||
// 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)
|
||||
}
|
||||
@@ -80,6 +84,23 @@ func NewHandler(ctx context.Context, pool *db.Pool, cfg config.Config) http.Hand
|
||||
sseSubs: make(map[*sseSubscriber]struct{}),
|
||||
}
|
||||
|
||||
// Wire Infisical backend when configured.
|
||||
if cfg.InfisicalSiteURL != "" {
|
||||
infCfg := secrets.InfisicalConfig{
|
||||
SiteURL: cfg.InfisicalSiteURL,
|
||||
ClientID: cfg.InfisicalClientID,
|
||||
ClientSecret: cfg.InfisicalClientSecret,
|
||||
ProjectID: cfg.InfisicalProjectID,
|
||||
SecretPath: "/",
|
||||
Env: cfg.InfisicalEnv,
|
||||
}
|
||||
if infCfg.Env == "" {
|
||||
infCfg.Env = "dev"
|
||||
}
|
||||
s.secretsManager = secrets.NewInfisicalBackend(infCfg)
|
||||
slog.Info("secrets backend wired", "backend", "infisical", "site", cfg.InfisicalSiteURL)
|
||||
}
|
||||
|
||||
// Start background SSE listener, tied to ctx for clean shutdown.
|
||||
// handleNotification (called per-message inside sseListener's loop) has
|
||||
// its own recover for the common case; this outer one covers the
|
||||
@@ -271,7 +292,7 @@ func NewHandler(ctx context.Context, pool *db.Pool, cfg config.Config) http.Hand
|
||||
if nomosAgentID == uuid.Nil && cfg.NomosAgentSlug != "" {
|
||||
_ = pool.QueryRow(ctx, "SELECT id FROM entities WHERE slug = $1", cfg.NomosAgentSlug).Scan(&nomosAgentID)
|
||||
}
|
||||
r.With(combinedAuth(cfg, false)).Handle("/mcp", mcphandler.NewHandler(pool, cfg.MCPBearerToken, nomosAgentID))
|
||||
r.With(combinedAuth(cfg, false)).Handle("/mcp", mcphandler.NewHandler(pool, cfg.MCPBearerToken, nomosAgentID, s.secretsManager))
|
||||
|
||||
if nomosURL := os.Getenv("NOMOS_PROXY_URL"); nomosURL != "" {
|
||||
target, _ := url.Parse(nomosURL)
|
||||
|
||||
Reference in New Issue
Block a user