0.27.0 — Infisical hardening: runtime refresh, audit logging, CLI verify/audit, startup verification, SSH host key verification, interface consolidation
This commit is contained in:
118
internal/secrets/config_overlay.go
Normal file
118
internal/secrets/config_overlay.go
Normal file
@@ -0,0 +1,118 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user