From 4155719422d416fb9af8cc6266697ebe16264538 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Tue, 25 Mar 2025 17:02:48 -0400 Subject: cmd/anubis: allow setting key bytes in flag/envvar (#97) * cmd/anubis: allow setting key bytes in flag/envvar Docs are updated to generate a random key on load and when people press the recycle button. Signed-off-by: Xe Iaso * review feedback fixups Signed-off-by: Xe Iaso * Update cmd/anubis/main.go Signed-off-by: Xe Iaso * Apply suggestions from code review Co-authored-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Signed-off-by: Xe Iaso --------- Signed-off-by: Xe Iaso Co-authored-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- cmd/anubis/main.go | 57 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 12 deletions(-) (limited to 'cmd') diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go index e493931..551ed61 100644 --- a/cmd/anubis/main.go +++ b/cmd/anubis/main.go @@ -2,6 +2,9 @@ package main import ( "context" + "crypto/ed25519" + "crypto/rand" + "encoding/hex" "flag" "fmt" "log" @@ -28,20 +31,34 @@ import ( ) var ( - bind = flag.String("bind", ":8923", "network address to bind HTTP to") - bindNetwork = flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp") - challengeDifficulty = flag.Int("difficulty", anubis.DefaultDifficulty, "difficulty of the challenge") - metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to") - metricsBindNetwork = flag.String("metrics-bind-network", "tcp", "network family for the metrics server to bind to") - socketMode = flag.String("socket-mode", "0770", "socket mode (permissions) for unix domain sockets.") - robotsTxt = flag.Bool("serve-robots-txt", false, "serve a robots.txt file that disallows all robots") - policyFname = flag.String("policy-fname", "", "full path to anubis policy document (defaults to a sensible built-in policy)") - slogLevel = flag.String("slog-level", "INFO", "logging level (see https://pkg.go.dev/log/slog#hdr-Levels)") - target = flag.String("target", "http://localhost:3923", "target to reverse proxy to") - healthcheck = flag.Bool("healthcheck", false, "run a health check against Anubis") - debugXRealIPDefault = flag.String("debug-x-real-ip-default", "", "If set, replace empty X-Real-Ip headers with this value, useful only for debugging Anubis and running it locally") + bind = flag.String("bind", ":8923", "network address to bind HTTP to") + bindNetwork = flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp") + challengeDifficulty = flag.Int("difficulty", anubis.DefaultDifficulty, "difficulty of the challenge") + ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned") + metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to") + metricsBindNetwork = flag.String("metrics-bind-network", "tcp", "network family for the metrics server to bind to") + socketMode = flag.String("socket-mode", "0770", "socket mode (permissions) for unix domain sockets.") + robotsTxt = flag.Bool("serve-robots-txt", false, "serve a robots.txt file that disallows all robots") + policyFname = flag.String("policy-fname", "", "full path to anubis policy document (defaults to a sensible built-in policy)") + slogLevel = flag.String("slog-level", "INFO", "logging level (see https://pkg.go.dev/log/slog#hdr-Levels)") + target = flag.String("target", "http://localhost:3923", "target to reverse proxy to") + healthcheck = flag.Bool("healthcheck", false, "run a health check against Anubis") + debugXRealIPDefault = flag.String("debug-x-real-ip-default", "", "If set, replace empty X-Real-Ip headers with this value, useful only for debugging Anubis and running it locally") ) +func keyFromHex(value string) (ed25519.PrivateKey, error) { + keyBytes, err := hex.DecodeString(value) + if err != nil { + return nil, fmt.Errorf("supplied key is not hex-encoded: %w", err) + } + + if len(keyBytes) != ed25519.SeedSize { + return nil, fmt.Errorf("supplied key is not %d bytes long, got %d bytes", ed25519.SeedSize, len(keyBytes)) + } + + return ed25519.NewKeyFromSeed(keyBytes), nil +} + func doHealthCheck() error { resp, err := http.Get("http://localhost" + *metricsBind + "/metrics") if err != nil { @@ -156,10 +173,26 @@ func main() { } fmt.Println() + var priv ed25519.PrivateKey + if *ed25519PrivateKeyHex == "" { + _, priv, err = ed25519.GenerateKey(rand.Reader) + if err != nil { + log.Fatalf("failed to generate ed25519 key: %v", err) + } + + slog.Warn("generating random key, Anubis will have strange behavior when multiple instances are behind the same load balancer target, for more information: see https://anubis.techaro.lol/docs/admin/installation#key-generation") + } else { + priv, err = keyFromHex(*ed25519PrivateKeyHex) + if err != nil { + log.Fatalf("failed to parse and validate ED25519_PRIVATE_KEY_HEX: %v", err) + } + } + s, err := libanubis.New(libanubis.Options{ Next: rp, Policy: policy, ServeRobotsTXT: *robotsTxt, + PrivateKey: priv, }) if err != nil { log.Fatalf("can't construct libanubis.Server: %v", err) -- cgit v1.2.3