119 lines
3.7 KiB
Go
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
|
|
}
|