Add config loader with strict stdlib-only TOML subset parser

Parse keyproxy.toml (listen address, [auth] token_ref, and the mpk-<name>
ref map) using a deliberate TOML subset implemented with the standard
library only. The surface is strict: unknown keys, malformed refs, and
backend-specific misconfigurations (e.g. env refs setting key, file refs
missing key) fail loudly at startup. The bearer-token ref must resolve
through the file backend. Parse errors carry line numbers, never line
contents.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
2026-08-28 22:30:50 -05:00
parent b43bc55963
commit 7bec2a1905
4 changed files with 705 additions and 0 deletions
+196
View File
@@ -0,0 +1,196 @@
package config
import (
"os"
"path/filepath"
"strings"
"testing"
)
const validConfig = `
listen = "127.0.0.1:9999"
[auth]
token_ref = "mpk-self"
[refs."mpk-self"]
backend = "file"
source = "/tmp/keyproxy.env"
key = "KEYPROXY_TOKEN"
[refs."mpk-example"]
backend = "file"
source = "/tmp/example.env"
key = "EXAMPLE_API_KEY"
[refs."mpk-example-env"]
backend = "env"
source = "EXAMPLE_API_KEY"
[refs."mpk-stub-bw"]
backend = "bitwarden"
source = "sm://p/example"
key = "EXAMPLE_API_KEY"
[refs."mpk-stub-vault"]
backend = "vault"
`
func writeConfig(t *testing.T, content string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "keyproxy.toml")
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatal(err)
}
return path
}
func TestLoadValid(t *testing.T) {
cfg, err := Load(writeConfig(t, validConfig))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if cfg.Listen != "127.0.0.1:9999" {
t.Fatalf("listen = %q", cfg.Listen)
}
if cfg.AuthTokenRef != "mpk-self" {
t.Fatalf("auth token ref = %q", cfg.AuthTokenRef)
}
if got := strings.Join(cfg.RefNames, ","); got != "mpk-example,mpk-example-env,mpk-self,mpk-stub-bw,mpk-stub-vault" {
t.Fatalf("ref names = %q", got)
}
file := cfg.Refs["mpk-example"]
if file.Backend != "file" || file.Source != "/tmp/example.env" || file.Key != "EXAMPLE_API_KEY" || file.Mode != "0600" {
t.Fatalf("file ref parsed wrong: %+v", file)
}
if cfg.Refs["mpk-stub-bw"].Backend != "bitwarden" {
t.Fatalf("stub ref parsed wrong: %+v", cfg.Refs["mpk-stub-bw"])
}
}
func TestLoadDefaults(t *testing.T) {
cfg, err := Load(writeConfig(t, strings.Replace(validConfig, `listen = "127.0.0.1:9999"`, "", 1)))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if cfg.Listen != DefaultListen {
t.Fatalf("default listen = %q, want %q", cfg.Listen, DefaultListen)
}
}
func TestLoadErrors(t *testing.T) {
tests := []struct {
name string
mutate func(string) string
wantErr string
}{
{
name: "missing file",
mutate: func(string) string { return "" },
wantErr: "read config",
},
{
name: "bad ref name",
mutate: func(s string) string { return strings.Replace(s, `mpk-example"`, `mpk_ExAMPLE"`, 1) },
wantErr: "must match mpk-",
},
{
name: "unknown backend",
mutate: func(s string) string { return strings.Replace(s, `backend = "env"`, `backend = "s3"`, 1) },
wantErr: "backend must be one of",
},
{
name: "file ref without source",
mutate: func(s string) string { return strings.Replace(s, `source = "/tmp/example.env"`, "", 1) },
wantErr: "file backend requires source",
},
{
name: "file ref without key",
mutate: func(s string) string { return strings.Replace(s, `key = "EXAMPLE_API_KEY"`, "", 1) },
wantErr: "file backend requires key",
},
{
name: "file ref with junk mode",
mutate: func(s string) string { return strings.Replace(s, `key = "EXAMPLE_API_KEY"`, `key = "EXAMPLE_API_KEY"`+"\n"+`mode = "readable"`, 1) },
wantErr: "mode must be an octal",
},
{
name: "env ref with key",
mutate: func(s string) string { return strings.Replace(s, `source = "EXAMPLE_API_KEY"`, `source = "EXAMPLE_API_KEY"`+"\n"+`key = "X"`, 1) },
wantErr: "env backend must not set key",
},
{
name: "env ref with mode",
mutate: func(s string) string { return strings.Replace(s, `source = "EXAMPLE_API_KEY"`, `source = "EXAMPLE_API_KEY"`+"\n"+`mode = "0644"`, 1) },
wantErr: "mode applies to the file backend only",
},
{
name: "unknown key inside ref table",
mutate: func(s string) string { return strings.Replace(s, `key = "EXAMPLE_API_KEY"`, `key = "EXAMPLE_API_KEY"`+"\n"+`ttl = "5m"`, 1) },
wantErr: "unknown key",
},
{
name: "no auth section",
mutate: func(s string) string { return strings.Replace(s, "token_ref = \"mpk-self\"", "", 1) },
wantErr: "token_ref is required",
},
{
name: "auth ref not configured",
mutate: func(s string) string { return strings.Replace(s, `token_ref = "mpk-self"`, `token_ref = "mpk-ghost"`, 1) },
wantErr: "not a configured ref",
},
{
name: "auth ref not file backend",
mutate: func(s string) string {
return strings.Replace(s, `token_ref = "mpk-self"`, `token_ref = "mpk-example-env"`, 1)
},
wantErr: "must use the file backend",
},
{
name: "no refs at all",
mutate: func(s string) string { return "listen = \"127.0.0.1:9999\"\n[auth]\ntoken_ref = \"mpk-self\"\n" },
wantErr: "no refs configured",
},
{
name: "unknown top-level key",
mutate: func(s string) string { return "ttl = \"5m\"\n" + s },
wantErr: "unknown top-level key",
},
{
name: "unknown auth key",
mutate: func(s string) string { return strings.Replace(s, "token_ref = \"mpk-self\"", "token_ref = \"mpk-self\"\nrotation = \"7d\"", 1) },
wantErr: "unknown key",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
content := tt.mutate(validConfig)
path := writeConfig(t, content)
if content == "" {
path = filepath.Join(t.TempDir(), "absent.toml")
}
cfg, err := Load(path)
if err == nil {
t.Fatalf("want error %q, got config %+v", tt.wantErr, cfg)
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("error %q does not contain %q", err, tt.wantErr)
}
})
}
}
func TestValidRef(t *testing.T) {
valid := []string{"mpk-a", "mpk-redmine", "mpk-a-1", "mpk-9"}
invalid := []string{"", "redmine", "MPK-a", "mpk-", "mpk--a", "mpk_a", "mpk-a_b", "mpk-a.b", "mpk-a b", "sk-live-pasted-secret"}
for _, r := range valid {
if !ValidRef(r) {
t.Errorf("ValidRef(%q) = false, want true", r)
}
}
for _, r := range invalid {
if ValidRef(r) {
t.Errorf("ValidRef(%q) = true, want false", r)
}
}
}