S7evinK: basicauth metrics (#961)
* Add setting to enable/disable metrics (#461) Add basic auth to /metric handlers Signed-off-by: Till Faelligen <tfaelligen@gmail.com> * Add warning message if metrics are exposed without protection * Remove redundant type conversion Signed-off-by: Till Faelligen <tfaelligen@gmail.com> * SetBasicAuth per test case * Update warning message and change loglevel to warn * Update common/config/config.go * Update dendrite-config.yaml Co-authored-by: Till Faelligen <tfaelligen@gmail.com> Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
This commit is contained in:
parent
2c43e222bd
commit
609f034bfb
|
@ -33,8 +33,8 @@ import (
|
||||||
"github.com/matrix-org/dendrite/publicroomsapi"
|
"github.com/matrix-org/dendrite/publicroomsapi"
|
||||||
"github.com/matrix-org/dendrite/roomserver"
|
"github.com/matrix-org/dendrite/roomserver"
|
||||||
"github.com/matrix-org/dendrite/syncapi"
|
"github.com/matrix-org/dendrite/syncapi"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -78,7 +78,9 @@ func main() {
|
||||||
|
|
||||||
// Set up the API endpoints we handle. /metrics is for prometheus, and is
|
// Set up the API endpoints we handle. /metrics is for prometheus, and is
|
||||||
// not wrapped by CORS, while everything else is
|
// not wrapped by CORS, while everything else is
|
||||||
http.Handle("/metrics", promhttp.Handler())
|
if cfg.Metrics.Enabled {
|
||||||
|
http.Handle("/metrics", common.WrapHandlerInBasicAuth(promhttp.Handler(), cfg.Metrics.BasicAuth))
|
||||||
|
}
|
||||||
http.Handle("/", httpHandler)
|
http.Handle("/", httpHandler)
|
||||||
|
|
||||||
// Expose the matrix APIs directly rather than putting them under a /api path.
|
// Expose the matrix APIs directly rather than putting them under a /api path.
|
||||||
|
|
|
@ -208,7 +208,7 @@ func (b *BaseDendrite) SetupAndServeHTTP(bindaddr string, listenaddr string) {
|
||||||
addr = listenaddr
|
addr = listenaddr
|
||||||
}
|
}
|
||||||
|
|
||||||
common.SetupHTTPAPI(http.DefaultServeMux, common.WrapHandlerInCORS(b.APIMux))
|
common.SetupHTTPAPI(http.DefaultServeMux, common.WrapHandlerInCORS(b.APIMux), b.Cfg)
|
||||||
logrus.Infof("Starting %s server on %s", b.componentName, addr)
|
logrus.Infof("Starting %s server on %s", b.componentName, addr)
|
||||||
|
|
||||||
err := http.ListenAndServe(addr, nil)
|
err := http.ListenAndServe(addr, nil)
|
||||||
|
|
|
@ -119,6 +119,19 @@ type Dendrite struct {
|
||||||
ThumbnailSizes []ThumbnailSize `yaml:"thumbnail_sizes"`
|
ThumbnailSizes []ThumbnailSize `yaml:"thumbnail_sizes"`
|
||||||
} `yaml:"media"`
|
} `yaml:"media"`
|
||||||
|
|
||||||
|
// The configuration to use for Prometheus metrics
|
||||||
|
Metrics struct {
|
||||||
|
// Whether or not the metrics are enabled
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
// Use BasicAuth for Authorization
|
||||||
|
BasicAuth struct {
|
||||||
|
// Authorization via Static Username & Password
|
||||||
|
// Hardcoded Username and Password
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
} `yaml:"basic_auth"`
|
||||||
|
} `yaml:"metrics"`
|
||||||
|
|
||||||
// The configuration for talking to kafka.
|
// The configuration for talking to kafka.
|
||||||
Kafka struct {
|
Kafka struct {
|
||||||
// A list of kafka addresses to connect to.
|
// A list of kafka addresses to connect to.
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
|
@ -13,8 +14,15 @@ import (
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BasicAuth is used for authorization on /metrics handlers
|
||||||
|
type BasicAuth struct {
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
// MakeAuthAPI turns a util.JSONRequestHandler function into an http.Handler which authenticates the request.
|
// MakeAuthAPI turns a util.JSONRequestHandler function into an http.Handler which authenticates the request.
|
||||||
func MakeAuthAPI(
|
func MakeAuthAPI(
|
||||||
metricsName string, data auth.Data,
|
metricsName string, data auth.Data,
|
||||||
|
@ -123,11 +131,34 @@ func MakeFedAPI(
|
||||||
|
|
||||||
// SetupHTTPAPI registers an HTTP API mux under /api and sets up a metrics
|
// SetupHTTPAPI registers an HTTP API mux under /api and sets up a metrics
|
||||||
// listener.
|
// listener.
|
||||||
func SetupHTTPAPI(servMux *http.ServeMux, apiMux http.Handler) {
|
func SetupHTTPAPI(servMux *http.ServeMux, apiMux http.Handler, cfg *config.Dendrite) {
|
||||||
servMux.Handle("/metrics", promhttp.Handler())
|
if cfg.Metrics.Enabled {
|
||||||
|
servMux.Handle("/metrics", WrapHandlerInBasicAuth(promhttp.Handler(), cfg.Metrics.BasicAuth))
|
||||||
|
}
|
||||||
servMux.Handle("/api/", http.StripPrefix("/api", apiMux))
|
servMux.Handle("/api/", http.StripPrefix("/api", apiMux))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WrapHandlerInBasicAuth adds basic auth to a handler. Only used for /metrics
|
||||||
|
func WrapHandlerInBasicAuth(h http.Handler, b BasicAuth) http.HandlerFunc {
|
||||||
|
if b.Username == "" || b.Password == "" {
|
||||||
|
logrus.Warn("Metrics are exposed without protection. Make sure you set up protection at proxy level.")
|
||||||
|
}
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Serve without authorization if either Username or Password is unset
|
||||||
|
if b.Username == "" || b.Password == "" {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
|
||||||
|
if !ok || user != b.Username || pass != b.Password {
|
||||||
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WrapHandlerInCORS adds CORS headers to all responses, including all error
|
// WrapHandlerInCORS adds CORS headers to all responses, including all error
|
||||||
// responses.
|
// responses.
|
||||||
// Handles OPTIONS requests directly.
|
// Handles OPTIONS requests directly.
|
||||||
|
|
95
common/httpapi_test.go
Normal file
95
common/httpapi_test.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWrapHandlerInBasicAuth(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
h http.Handler
|
||||||
|
b BasicAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
dummyHandler := http.HandlerFunc(func(h http.ResponseWriter, r *http.Request) {
|
||||||
|
h.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want int
|
||||||
|
reqAuth bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no user or password setup",
|
||||||
|
args: args{h: dummyHandler},
|
||||||
|
want: http.StatusOK,
|
||||||
|
reqAuth: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only user set",
|
||||||
|
args: args{
|
||||||
|
h: dummyHandler,
|
||||||
|
b: BasicAuth{Username: "test"}, // no basic auth
|
||||||
|
},
|
||||||
|
want: http.StatusOK,
|
||||||
|
reqAuth: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only pass set",
|
||||||
|
args: args{
|
||||||
|
h: dummyHandler,
|
||||||
|
b: BasicAuth{Password: "test"}, // no basic auth
|
||||||
|
},
|
||||||
|
want: http.StatusOK,
|
||||||
|
reqAuth: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "credentials correct",
|
||||||
|
args: args{
|
||||||
|
h: dummyHandler,
|
||||||
|
b: BasicAuth{Username: "test", Password: "test"}, // basic auth enabled
|
||||||
|
},
|
||||||
|
want: http.StatusOK,
|
||||||
|
reqAuth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "credentials wrong",
|
||||||
|
args: args{
|
||||||
|
h: dummyHandler,
|
||||||
|
b: BasicAuth{Username: "test1", Password: "test"}, // basic auth enabled
|
||||||
|
},
|
||||||
|
want: http.StatusForbidden,
|
||||||
|
reqAuth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no basic auth in request",
|
||||||
|
args: args{
|
||||||
|
h: dummyHandler,
|
||||||
|
b: BasicAuth{Username: "test", Password: "test"}, // basic auth enabled
|
||||||
|
},
|
||||||
|
want: http.StatusForbidden,
|
||||||
|
reqAuth: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
baHandler := WrapHandlerInBasicAuth(tt.args.h, tt.args.b)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://localhost/metrics", nil)
|
||||||
|
if tt.reqAuth {
|
||||||
|
req.SetBasicAuth("test", "test")
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
baHandler(w, req)
|
||||||
|
resp := w.Result()
|
||||||
|
|
||||||
|
if resp.StatusCode != tt.want {
|
||||||
|
t.Errorf("Expected status code %d, got %d", resp.StatusCode, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,6 +53,15 @@ media:
|
||||||
height: 600
|
height: 600
|
||||||
method: scale
|
method: scale
|
||||||
|
|
||||||
|
# Metrics config for Prometheus
|
||||||
|
metrics:
|
||||||
|
# Whether or not metrics are enabled
|
||||||
|
enabled: false
|
||||||
|
# Use basic auth to protect the metrics. Uncomment to the complete block to enable.
|
||||||
|
#basic_auth:
|
||||||
|
# username: prometheusUser
|
||||||
|
# password: y0ursecr3tPa$$w0rd
|
||||||
|
|
||||||
# The config for the TURN server
|
# The config for the TURN server
|
||||||
turn:
|
turn:
|
||||||
# Whether or not guests can request TURN credentials
|
# Whether or not guests can request TURN credentials
|
||||||
|
|
Loading…
Reference in a new issue