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
78 lines
2.0 KiB
Go
78 lines
2.0 KiB
Go
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
|
|
}
|