diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 96793f27..75186865 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -28,7 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Enable [HTTP basic auth](./admin/policies.mdx#http-basic-authentication) for the metrics server. - Fix a bug in the dataset poisoning maze that could allow denial of service [#1580](https://github.com/TecharoHQ/anubis/issues/1580). - Add config option to add ASN to logs/metrics. -- Log weight when issuing challenge +- Log weight when issuing challenge. +- Gate pprof endpoints behind `metrics.debug` in the policy file. - Fix `path_regex` and CEL `path` rules not matching when using Traefik `forwardAuth` middleware. Anubis now checks `X-Forwarded-Uri` (Traefik) in addition to `X-Original-URI` (nginx) when resolving the request path in subrequest mode ([#1628](https://github.com/TecharoHQ/anubis/issues/1628)). ## v1.25.0: Necron diff --git a/docs/docs/admin/policies.mdx b/docs/docs/admin/policies.mdx index be53550d..53119013 100644 --- a/docs/docs/admin/policies.mdx +++ b/docs/docs/admin/policies.mdx @@ -138,6 +138,24 @@ metrics: socketMode: "0700" # must be a string ``` +### Debug routes + +Anubis' metrics server supports [pprof](https://pkg.go.dev/runtime/pprof), the Go standard library tool for profiling Go applications. This is very useful for debugging how Anubis works in the wild with regards to CPU, multicore, and RAM usage. pprof is very powerful and can expose command line arguments as part of the debugging setup (inside Google, everything is done with command line flags). + +Prior versions of Anubis exposed pprof endpoints on all TCP bindhosts by default. This means that machines with incorrectly configured firewalls can expose command line arguments to the public internet in the right conditions. + +In order to enable pprof profiling endpoints on the Metrics server, set the `debug` flag under the `metrics` block: + +```yaml +metrics: + bind: ":9090" + network: "tcp" + + debug: true +``` + +To err on the side of caution, this defaults to disabled. If this defaults migration breaks your configuration, please let us know in a ticket. + ### TLS If you want to serve the metrics server over TLS, use the `tls` block: diff --git a/lib/config/metrics.go b/lib/config/metrics.go index 27316808..1d43add0 100644 --- a/lib/config/metrics.go +++ b/lib/config/metrics.go @@ -32,6 +32,7 @@ type Metrics struct { Network string `json:"network" yaml:"network"` SocketMode string `json:"socketMode" yaml:"socketMode"` TLS *MetricsTLS `json:"tls" yaml:"tls"` + Debug bool `json:"debug" yaml:"debug"` BasicAuth *MetricsBasicAuth `json:"basicAuth" yaml:"basicAuth"` } diff --git a/lib/metrics/metrics.go b/lib/metrics/metrics.go index 47b9467d..871188aa 100644 --- a/lib/metrics/metrics.go +++ b/lib/metrics/metrics.go @@ -34,11 +34,15 @@ func (s *Server) Run(ctx context.Context, done func()) { func (s *Server) run(ctx context.Context, lg *slog.Logger) error { mux := http.NewServeMux() - mux.HandleFunc("GET /debug/pprof/", pprof.Index) - mux.HandleFunc("GET /debug/pprof/cmdline", pprof.Cmdline) - mux.HandleFunc("GET /debug/pprof/profile", pprof.Profile) - mux.HandleFunc("GET /debug/pprof/symbol", pprof.Symbol) - mux.HandleFunc("GET /debug/pprof/trace", pprof.Trace) + + if s.Config.Debug { + mux.HandleFunc("GET /debug/pprof/", pprof.Index) + mux.HandleFunc("GET /debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("GET /debug/pprof/profile", pprof.Profile) + mux.HandleFunc("GET /debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("GET /debug/pprof/trace", pprof.Trace) + } + mux.Handle("/metrics", promhttp.Handler()) mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { st, ok := internal.GetHealth("anubis") diff --git a/lib/metrics/metrics_test.go b/lib/metrics/metrics_test.go new file mode 100644 index 00000000..ba1f584a --- /dev/null +++ b/lib/metrics/metrics_test.go @@ -0,0 +1,49 @@ +package metrics + +import ( + "context" + "io" + "log/slog" + "net" + "net/http" + "strings" + "testing" + "time" + + "github.com/TecharoHQ/anubis/lib/config" +) + +func TestMetricsPprofCmdlineExposedWithoutAuthentication(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + addr := ln.Addr().String() + _ = ln.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + done := make(chan struct{}) + srv := &Server{ + Config: &config.Metrics{Network: "tcp", Bind: addr}, + Log: slog.Default(), + } + go srv.Run(ctx, func() { close(done) }) + + url := "http://" + addr + "/debug/pprof/cmdline" + var body []byte + resp, err := http.Get(url) + if err == nil { + body, err = io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("can't read body: %v", err) + } + defer resp.Body.Close() + } + time.Sleep(50 * time.Millisecond) + if strings.Contains(string(body), "metrics.test") { + t.Fatalf("pprof is enabled by default, cmdline process arguments: %q", string(body)) + } + cancel() + <-done +}