Add keyproxy CLI with serve and get commands
Wire the config loader, backends, and HTTP server into an executable.
`serve` runs the resolve hop until SIGINT/SIGTERM (graceful shutdown);
`get` resolves one ref to stdout as a bare value with no trailing
newline for $(...) exec-style plumbing. Exit codes: 0 ok, 1 usage or
config error, 2 resolution failure.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
// Command keyproxy resolves opaque mpk- refs to key material at the
|
||||
// wire. v0 backends: file (0600 env files) and env (process env);
|
||||
// bitwarden and vault ship as explicit not-implemented stubs until
|
||||
// phase 3. Material is memory-only: never persisted, never logged.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"git.knownelement.com/ukrrs/mopac-keyproxy/internal/backend"
|
||||
"git.knownelement.com/ukrrs/mopac-keyproxy/internal/config"
|
||||
"git.knownelement.com/ukrrs/mopac-keyproxy/internal/server"
|
||||
)
|
||||
|
||||
const usage = `keyproxy: resolve mpk- placeholder refs to key material
|
||||
|
||||
Usage:
|
||||
keyproxy serve [-config PATH] [-listen ADDR] run the resolve HTTP hop
|
||||
keyproxy get REF [-config PATH] resolve one ref to stdout
|
||||
keyproxy help print this usage
|
||||
|
||||
Backends (v0): file (0600 env files), env (process env); bitwarden and
|
||||
vault are explicit not-implemented stubs until phase 3. Material is
|
||||
memory-only: never persisted, never logged.
|
||||
|
||||
Exit codes: 0 ok, 1 usage/config error, 2 resolution failure.
|
||||
`
|
||||
|
||||
func main() {
|
||||
os.Exit(run(os.Args[1:]))
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprint(os.Stderr, usage)
|
||||
return 1
|
||||
}
|
||||
switch args[0] {
|
||||
case "help", "-h", "--help":
|
||||
fmt.Print(usage)
|
||||
return 0
|
||||
case "serve":
|
||||
return cmdServe(args[1:])
|
||||
case "get":
|
||||
return cmdGet(args[1:])
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "keyproxy: unknown command %q\n\n%s", args[0], usage)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// newRegistry wires every backend behind one interface; phase-3
|
||||
// connectors replace the stubs in this single place.
|
||||
func newRegistry() *backend.Registry {
|
||||
return backend.NewRegistry(
|
||||
backend.NewFile(),
|
||||
backend.NewEnv(),
|
||||
backend.NewBitwarden(),
|
||||
backend.NewVault(),
|
||||
)
|
||||
}
|
||||
|
||||
func loadConfig(path string) (*config.Config, error) {
|
||||
if path == "" {
|
||||
path = os.Getenv("KEYPROXY_CONFIG")
|
||||
}
|
||||
if path == "" {
|
||||
path = "keyproxy.toml"
|
||||
}
|
||||
return config.Load(path)
|
||||
}
|
||||
|
||||
func cmdServe(args []string) int {
|
||||
fs := flag.NewFlagSet("serve", flag.ContinueOnError)
|
||||
configPath := fs.String("config", "", "config file (default $KEYPROXY_CONFIG, then ./keyproxy.toml)")
|
||||
listen := fs.String("listen", "", "bind address (overrides config listen)")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
cfg, err := loadConfig(*configPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keyproxy: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if *listen != "" {
|
||||
cfg.Listen = *listen
|
||||
}
|
||||
srv, err := server.New(cfg, newRegistry(), os.Stdout)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keyproxy: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
if err := srv.ListenAndServe(ctx); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keyproxy: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdGet(args []string) int {
|
||||
fs := flag.NewFlagSet("get", flag.ContinueOnError)
|
||||
configPath := fs.String("config", "", "config file (default $KEYPROXY_CONFIG, then ./keyproxy.toml)")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
if fs.NArg() != 1 {
|
||||
fmt.Fprintln(os.Stderr, "keyproxy get: exactly one ref required (mpk-<name>)")
|
||||
return 1
|
||||
}
|
||||
name := fs.Arg(0)
|
||||
cfg, err := loadConfig(*configPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keyproxy: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
ref, ok := cfg.Refs[name]
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "keyproxy: ref %s: unknown_ref\n", name)
|
||||
return 2
|
||||
}
|
||||
b, ok := newRegistry().Get(ref.Backend)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "keyproxy: ref %s: backend %s not registered\n", name, ref.Backend)
|
||||
return 2
|
||||
}
|
||||
value, err := b.Resolve(context.Background(), ref)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "keyproxy: %v\n", backend.AsResolveError(err, ref, ref.Backend))
|
||||
return 2
|
||||
}
|
||||
// No trailing newline: exec-style plumbing ($(keyproxy get mpk-x))
|
||||
// wants the bare value.
|
||||
fmt.Print(value)
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user