Add backend resolver interface with file and env backends plus phase-3 stubs

Introduce the single Backend interface every credential source implements.
v0 ships two working backends: file (0600 KEY=VALUE env files, parsed in
pure Go, never sourced; looser permission masks refused before read) and
env (process-environment indirection). Bitwarden Secrets Manager and
HashiCorp Vault ship as explicit not-implemented stubs behind the same
interface so the phase-3 connectors are drop-ins. All failures are typed
ResolveErrors carrying only the ref, backend, and a fixed reason enum —
never material.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
2026-08-28 22:30:50 -05:00
parent 325a36cc35
commit b43bc55963
10 changed files with 730 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
package backend
import (
"context"
"os"
"strconv"
"strings"
)
// File resolves refs from env files on disk (KEY=VALUE lines, parsed in
// Go, never sourced). House rule: secret files are 0600. A file whose
// permission bits reach beyond the allowed mask (default 0600; stricter
// masks such as 0400 always pass) is refused before it is read. Material
// is read on demand per resolve and kept in memory only.
type File struct{}
// NewFile returns the file backend.
func NewFile() *File { return &File{} }
// Name implements Backend.
func (f *File) Name() string { return "file" }
// Resolve implements Backend.
func (f *File) Resolve(ctx context.Context, ref Ref) (string, error) {
path := expandHome(ref.Source)
info, err := os.Stat(path)
if err != nil || info.IsDir() {
return "", Err(ref, f.Name(), ReasonUnreadableSource)
}
if info.Mode().Perm()&^allowedMode(ref.Mode) != 0 {
return "", Err(ref, f.Name(), ReasonInsecureMode)
}
data, err := os.ReadFile(path)
if err != nil {
return "", Err(ref, f.Name(), ReasonUnreadableSource)
}
kv, err := parseEnvFile(data)
if err != nil {
return "", Err(ref, f.Name(), ReasonMalformedSource)
}
v, ok := kv[ref.Key]
if !ok {
return "", Err(ref, f.Name(), ReasonMissingKey)
}
if v == "" {
return "", Err(ref, f.Name(), ReasonEmptyValue)
}
return v, nil
}
// allowedMode parses ref.Mode ("0600"-style octal, default 0600) into a
// permission mask; unparsable masks fail closed to 0 (nothing allowed).
func allowedMode(mode string) os.FileMode {
if mode == "" {
return 0o600
}
m, err := strconv.ParseUint(strings.TrimPrefix(mode, "0o"), 8, 32)
if err != nil {
return 0
}
return os.FileMode(m) & os.ModePerm
}
// expandHome resolves a leading ~ / ~/ to the user's home directory;
// every other path is returned unchanged.
func expandHome(path string) string {
if path == "~" || strings.HasPrefix(path, "~/") {
home, err := os.UserHomeDir()
if err == nil {
if path == "~" {
return home
}
return home + path[1:]
}
}
return path
}