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
197 lines
5.7 KiB
Go
197 lines
5.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|