Files
oikos/internal/secrets/config_overlay.go
dtoro c9d506b0f8
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
0.27.0 — Infisical hardening: runtime refresh, audit logging, CLI verify/audit, startup verification, SSH host key verification, interface consolidation
2026-08-05 23:51:01 +02:00

119 lines
3.7 KiB
Go

package secrets
import (
"context"
"log/slog"
)
// NewManagerFromConfig creates a secrets Manager from the Infisical connection
// parameters in cfg, with an optional SOPS fallback from cfg.SecretsDir.
// Returns nil if Infisical is not configured.
func NewManagerFromConfig(siteURL, clientID, clientSecret, projectID, env, secretsDir string) *Manager {
if siteURL == "" {
return nil
}
infCfg := InfisicalConfig{
SiteURL: siteURL,
ClientID: clientID,
ClientSecret: clientSecret,
ProjectID: projectID,
SecretPath: "/",
Env: env,
}
if infCfg.Env == "" {
infCfg.Env = "dev"
}
primary := NewInfisicalBackend(infCfg)
var fallback Backend
if secretsDir != "" {
fallback = NewSOPSBackend(secretsDir)
}
return NewManager(primary, fallback)
}
// VerifyExpectedSecrets checks that a list of expected keys are present
// in the backend. Logs a summary and returns the count of missing keys.
// Use at startup to detect incomplete Infisical migration.
func VerifyExpectedSecrets(ctx context.Context, sec Backend, expected []string) int {
keys, err := sec.List(ctx)
if err != nil {
slog.Warn("secrets: cannot verify expected secrets, list failed", "error", err)
return len(expected)
}
keySet := make(map[string]struct{}, len(keys))
for _, k := range keys {
keySet[k] = struct{}{}
}
missing := 0
for _, exp := range expected {
if _, ok := keySet[exp]; !ok {
missing++
slog.Warn("secrets: expected key missing from Infisical", "key", exp)
}
}
if missing == 0 {
slog.Info("secrets: all expected keys present", "count", len(expected))
} else {
slog.Warn("secrets: some expected keys missing from Infisical",
"missing", missing, "total", len(expected))
}
return missing
}
// OverlayConfig fetches secrets from the backend and returns a function that
// applies them to config fields. Each entry maps an Infisical key to a setter;
// if the key is found and non-empty, the setter is called; if not found or
// empty, the env-derived value is left unchanged and a warning is logged.
// Returns the number of secrets resolved from Infisical (useful for logging).
func OverlayConfig(ctx context.Context, sec Backend, overlays []secretOverlay) int {
resolved := 0
for _, o := range overlays {
val, err := sec.Get(ctx, o.infisicalKey)
if err != nil {
slog.Warn("secret not resolved from Infisical, using env fallback",
"key", o.infisicalKey, "error", err)
continue
}
if val == "" {
slog.Warn("Infisical returned empty value, keeping env-derived value",
"key", o.infisicalKey)
continue
}
o.apply(val)
resolved++
}
return resolved
}
// ConfigOverlays returns the standard set of Infisical → config overlays for
// the oikos binary. Each overlay is attempted at startup; if the key exists
// in Infisical, it overrides the env-derived value.
func ConfigOverlays(cfg map[string]func(string)) []secretOverlay {
overlays := make([]secretOverlay, 0, len(cfg))
for key, setter := range cfg {
overlays = append(overlays, secretOverlay{infisicalKey: key, apply: setter})
}
return overlays
}
// ResolveSecret attempts to fetch a single secret from the backend. Returns
// the Infisical value if found and non-empty, otherwise falls back to the
// env-derived value. Warnings are logged for failures.
func ResolveSecret(ctx context.Context, sec Backend, infisicalKey, fallback string) string {
if sec == nil {
return fallback
}
val, err := sec.Get(ctx, infisicalKey)
if err != nil {
slog.Warn("secret not resolved from Infisical, using env fallback",
"key", infisicalKey, "error", err)
return fallback
}
if val == "" {
slog.Warn("Infisical returned empty value, keeping env-derived value",
"key", infisicalKey)
return fallback
}
return val
}