diff --git a/app/app.go b/app/app.go index 0157540..43e32c8 100644 --- a/app/app.go +++ b/app/app.go @@ -169,6 +169,24 @@ func (a *App) makeMetricsProxy() *proxmetrics.Fetcher { ) } +func (a *App) makeStatusHandler() *proxmetrics.StatusHandler { + _, local4, _ := net.ParseCIDR("127.0.0.1/32") // nolint:errcheck + _, local6, _ := net.ParseCIDR("::1/128") // nolint:errcheck + allowed := []*config.CIDR{ + { + IpNet: local4, + }, + { + IpNet: local6, + }, + } + return proxmetrics.NewStatusHandler( + proxmetrics.NewStatusCollector(a.gslocConsul), + append(allowed, a.cnf.MetricsConfig.AllowedInspect...), + a.cnf.MetricsConfig.TrustXFF, + ) +} + func (a *App) loadGslocConsul() error { if a.onlyServeDns { a.entry.Info("Only serve DNS: no gsloc consul for api") @@ -264,7 +282,7 @@ func (a *App) Run() error { grpcServer := servers.NewHTTPServer( a.cnf.HTTPServer, a.hcHandler, a.grpcServer, - a.makeMetricsProxy(), proxmetrics.NewStatusCollector(a.gslocConsul), + a.makeMetricsProxy(), a.makeStatusHandler(), ) grpcServer.Run(a.ctx) }() diff --git a/config/metrics.go b/config/metrics.go index 2c8b699..7b416de 100644 --- a/config/metrics.go +++ b/config/metrics.go @@ -8,6 +8,8 @@ import ( var validateNameForPath = regexp.MustCompile(`(\s|_)`).MatchString type MetricsConfig struct { + AllowedInspect []*CIDR `yaml:"allowed_inspect"` + TrustXFF bool `yaml:"trust_xff"` ProxyMetricsConfig *ProxyMetricsConfig `yaml:"proxy"` } @@ -22,6 +24,15 @@ func (u *MetricsConfig) init() error { return nil } +func (u *MetricsConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain MetricsConfig + err := unmarshal((*plain)(u)) + if err != nil { + return err + } + return u.init() +} + type ProxyMetricsConfig struct { Targets []*ProxyMetricsTarget `yaml:"targets"` } diff --git a/proxmetrics/status.go b/proxmetrics/status.go index 3ced6e6..5cc00d0 100644 --- a/proxmetrics/status.go +++ b/proxmetrics/status.go @@ -2,11 +2,14 @@ package proxmetrics import ( gslbsvc "github.com/orange-cloudfoundry/gsloc-go-sdk/gsloc/services/gslb/v1" + "github.com/orange-cloudfoundry/gsloc/config" "github.com/orange-cloudfoundry/gsloc/disco" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" log "github.com/sirupsen/logrus" + "net" "net/http" + "strings" ) type StatusCollector struct { @@ -14,12 +17,58 @@ type StatusCollector struct { gslocConsul *disco.GslocConsul } -func StatusHandler(collector *StatusCollector) http.Handler { +type StatusHandler struct { + metricHandler http.Handler + allowedInspect []*config.CIDR + trustXFF bool +} + +func NewStatusHandler(collector *StatusCollector, allowedInspect []*config.CIDR, trustXFF bool) *StatusHandler { registry := prometheus.NewRegistry() registry.MustRegister(collector) - return promhttp.InstrumentMetricHandler( - registry, promhttp.HandlerFor(registry, promhttp.HandlerOpts{}), - ) + return &StatusHandler{ + metricHandler: promhttp.InstrumentMetricHandler( + registry, promhttp.HandlerFor(registry, promhttp.HandlerOpts{}), + ), + allowedInspect: allowedInspect, + trustXFF: trustXFF, + } +} + +func (s *StatusHandler) isAllowedInspect(req *http.Request) bool { + if s.remoteAddrIsAllowed(req.RemoteAddr) { + return true + } + if !s.trustXFF || req.Header.Get("X-Forwarded-For") == "" { + return false + } + allIps := strings.Split(req.Header.Get("X-Forwarded-For"), ",") + if s.remoteAddrIsAllowed(allIps[0]) { + return true + } + return false +} + +func (s *StatusHandler) remoteAddrIsAllowed(remoteAddr string) bool { + if strings.Contains(remoteAddr, ":") { + remoteAddr, _, _ = net.SplitHostPort(remoteAddr) // nolint: errcheck + } + remoteAddrIp := net.ParseIP(remoteAddr) + for _, cidr := range s.allowedInspect { + if cidr.IpNet.Contains(remoteAddrIp) { + return true + } + } + return false +} + +func (s *StatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if !s.isAllowedInspect(r) { + http.Error(w, "Not allowed", http.StatusForbidden) + return + } + s.metricHandler.ServeHTTP(w, r) + return } func NewStatusCollector(gslocConsul *disco.GslocConsul) *StatusCollector { diff --git a/servers/http.go b/servers/http.go index 2dcf3ca..4cc352b 100644 --- a/servers/http.go +++ b/servers/http.go @@ -16,12 +16,12 @@ import ( ) type HTTPServer struct { - mux *mux.Router - cnf *config.HTTPServerConfig - hcker *healthchecks.HcHandler - grpcServ *grpc.Server - metricsFetcher *proxmetrics.Fetcher - statusCollector *proxmetrics.StatusCollector + mux *mux.Router + cnf *config.HTTPServerConfig + hcker *healthchecks.HcHandler + grpcServ *grpc.Server + metricsFetcher *proxmetrics.Fetcher + statusHandler *proxmetrics.StatusHandler } func NewHTTPServer( @@ -29,15 +29,15 @@ func NewHTTPServer( hcker *healthchecks.HcHandler, grpcServ *grpc.Server, metricsFetcher *proxmetrics.Fetcher, - statusCollector *proxmetrics.StatusCollector, + statusHandler *proxmetrics.StatusHandler, ) *HTTPServer { return &HTTPServer{ - mux: mux.NewRouter(), - cnf: cnf, - hcker: hcker, - grpcServ: grpcServ, - metricsFetcher: metricsFetcher, - statusCollector: statusCollector, + mux: mux.NewRouter(), + cnf: cnf, + hcker: hcker, + grpcServ: grpcServ, + metricsFetcher: metricsFetcher, + statusHandler: statusHandler, } } @@ -55,7 +55,7 @@ func (s *HTTPServer) ServeHTTP(writer http.ResponseWriter, request *http.Request func (s *HTTPServer) Run(ctx context.Context) { s.mux.Path("/metrics").Handler(s.metricsFetcher) - s.mux.Path("/metrics/status").Handler(proxmetrics.StatusHandler(s.statusCollector)) + s.mux.Path("/metrics/status").Handler(s.statusHandler) s.mux.Methods("POST").Path("/hc/{fqdn}/member/{ip}").Handler(s.hcker) srvTls := &http.Server{