Files
oikos/internal/secrets/sops.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

94 lines
2.1 KiB
Go

package secrets
import (
"context"
"fmt"
"log/slog"
"os"
"os/exec"
"strings"
"gopkg.in/yaml.v3"
)
// SOPSBackend reads secrets from SOPS-encrypted YAML files using the sops CLI.
// This is the DR fallback after Infisical migration.
type SOPSBackend struct {
secretsDir string
secrets map[string]string
loaded bool
}
// NewSOPSBackend creates a SOPS backend reading from a directory of .yaml files.
func NewSOPSBackend(secretsDir string) *SOPSBackend {
return &SOPSBackend{secretsDir: secretsDir}
}
func (s *SOPSBackend) Name() string { return "sops" }
func (s *SOPSBackend) load() error {
if s.loaded {
return nil
}
s.secrets = make(map[string]string)
entries, err := os.ReadDir(s.secretsDir)
if err != nil {
return fmt.Errorf("read secrets dir: %w", err)
}
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yaml") {
continue
}
path := s.secretsDir + "/" + entry.Name()
cmd := exec.Command("sops", "-d", path)
out, err := cmd.Output()
if err != nil {
slog.Warn("sops: decryption failed", "file", entry.Name(), "error", err)
continue // skip unreadable files
}
var data map[string]any
if err := yaml.Unmarshal(out, &data); err != nil {
slog.Warn("sops: invalid yaml after decryption", "file", entry.Name(), "error", err)
continue
}
base := strings.TrimSuffix(entry.Name(), ".yaml")
for k, v := range data {
s.secrets[base+"/"+k] = fmt.Sprintf("%v", v)
}
}
s.loaded = true
return nil
}
func (s *SOPSBackend) Get(ctx context.Context, key string) (string, error) {
if err := s.load(); err != nil {
return "", err
}
val, ok := s.secrets[key]
if !ok {
return "", ErrNotFound
}
return val, nil
}
func (s *SOPSBackend) List(ctx context.Context) ([]string, error) {
if err := s.load(); err != nil {
return nil, err
}
keys := make([]string, 0, len(s.secrets))
for k := range s.secrets {
keys = append(keys, k)
}
return keys, nil
}
func (s *SOPSBackend) Set(ctx context.Context, key string, value string) error {
return fmt.Errorf("SOPS backend is read-only; use Infisical for writes or sops CLI manually")
}