feat(metrics): enable TLS/mTLS serving support (#1576)

* feat(config): add metrics TLS configuration

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(metrics): add naive TLS serving for metrics

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(metrics): import keypairreloader from a private project

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(metrics): properly surface errors with the metrics server

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(config): add CA certificate config value

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(metrics): enable mTLS support

Signed-off-by: Xe Iaso <me@xeiaso.net>

* doc(default-config): document how to set up TLS and mTLS

Signed-off-by: Xe Iaso <me@xeiaso.net>

* doc: document metrics TLS and mTLS

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Xe Iaso
2026-04-22 19:55:09 -04:00
committed by GitHub
parent f21706eb12
commit 8f8ae76d56
17 changed files with 665 additions and 12 deletions
+45 -3
View File
@@ -2,11 +2,14 @@ package metrics
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"log/slog"
"net/http"
"net/http/pprof"
"os"
"time"
"github.com/TecharoHQ/anubis/internal"
@@ -20,10 +23,16 @@ type Server struct {
Log *slog.Logger
}
func (s *Server) Run(ctx context.Context, done func()) error {
func (s *Server) Run(ctx context.Context, done func()) {
defer done()
lg := s.Log.With("subsystem", "metrics")
if err := s.run(ctx, lg); err != nil {
lg.Error("can't serve metrics server", "err", err)
}
}
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)
@@ -62,6 +71,32 @@ func (s *Server) Run(ctx context.Context, done func()) error {
defer ln.Close()
if s.Config.TLS != nil {
kpr, err := NewKeypairReloader(s.Config.TLS.Certificate, s.Config.TLS.Key, lg)
if err != nil {
return fmt.Errorf("can't setup keypair reloader: %w", err)
}
srv.TLSConfig = &tls.Config{
GetCertificate: kpr.GetCertificate,
}
if s.Config.TLS.CA != "" {
caCert, err := os.ReadFile(s.Config.TLS.CA)
if err != nil {
return fmt.Errorf("%w %s: %w", config.ErrCantReadFile, s.Config.TLS.CA, err)
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(caCert) {
return fmt.Errorf("%w %s", config.ErrInvalidMetricsCACertificate, s.Config.TLS.CA)
}
srv.TLSConfig.ClientCAs = certPool
srv.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
}
lg.Debug("listening for metrics", "url", metricsURL)
go func() {
@@ -73,8 +108,15 @@ func (s *Server) Run(ctx context.Context, done func()) error {
}
}()
if err := srv.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("can't serve metrics server: %w", err)
switch s.Config.TLS != nil {
case true:
if err := srv.ServeTLS(ln, "", ""); !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("can't serve TLS metrics server: %w", err)
}
case false:
if err := srv.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("can't serve metrics server: %w", err)
}
}
return nil