Merge branch 'master' into p2p

This commit is contained in:
Neil Alexander 2020-04-14 15:54:43 +01:00 committed by GitHub
commit 94de7c1468
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 173 additions and 7 deletions

View file

@ -362,7 +362,7 @@ func UsernameMatchesMultipleExclusiveNamespaces(
// Check namespaces and see if more than one match
matchCount := 0
for _, appservice := range cfg.Derived.ApplicationServices {
if appservice.IsInterestedInUserID(userID) {
if appservice.OwnsNamespaceCoveringUserId(userID) {
if matchCount++; matchCount > 1 {
return true
}
@ -1000,7 +1000,7 @@ func RegisterAvailable(
// Check if this username is reserved by an application service
userID := userutil.MakeUserID(username, cfg.Matrix.ServerName)
for _, appservice := range cfg.Derived.ApplicationServices {
if appservice.IsInterestedInUserID(userID) {
if appservice.OwnsNamespaceCoveringUserId(userID) {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.UserInUse("Desired user ID is reserved by an application service."),

View file

@ -34,8 +34,8 @@ import (
"github.com/matrix-org/dendrite/publicroomsapi/storage"
"github.com/matrix-org/dendrite/roomserver"
"github.com/matrix-org/dendrite/syncapi"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
)
@ -83,7 +83,9 @@ func main() {
// Set up the API endpoints we handle. /metrics is for prometheus, and 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)
// Expose the matrix APIs directly rather than putting them under a /api path.

View file

@ -208,7 +208,7 @@ func (b *BaseDendrite) SetupAndServeHTTP(bindaddr string, listenaddr string) {
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)
err := http.ListenAndServe(addr, nil)

View file

@ -98,6 +98,22 @@ func (a *ApplicationService) IsInterestedInUserID(
return false
}
// OwnsNamespaceCoveringUserId returns a bool on whether an application service's
// namespace is exclusive and includes the given user ID
func (a *ApplicationService) OwnsNamespaceCoveringUserId(
userID string,
) bool {
if namespaceSlice, ok := a.NamespaceMap["users"]; ok {
for _, namespace := range namespaceSlice {
if namespace.Exclusive && namespace.RegexpObject.MatchString(userID) {
return true
}
}
}
return false
}
// IsInterestedInRoomAlias returns a bool on whether an application service's
// namespace includes the given room alias
func (a *ApplicationService) IsInterestedInRoomAlias(

View file

@ -119,6 +119,19 @@ type Dendrite struct {
ThumbnailSizes []ThumbnailSize `yaml:"thumbnail_sizes"`
} `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.
Kafka struct {
// A list of kafka addresses to connect to.

View file

@ -6,6 +6,7 @@ import (
"github.com/matrix-org/dendrite/clientapi/auth"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/common/config"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
opentracing "github.com/opentracing/opentracing-go"
@ -13,8 +14,15 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"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.
func MakeAuthAPI(
metricsName string, data auth.Data,
@ -123,11 +131,34 @@ func MakeFedAPI(
// SetupHTTPAPI registers an HTTP API mux under /api and sets up a metrics
// listener.
func SetupHTTPAPI(servMux *http.ServeMux, apiMux http.Handler) {
servMux.Handle("/metrics", promhttp.Handler())
func SetupHTTPAPI(servMux *http.ServeMux, apiMux http.Handler, cfg *config.Dendrite) {
if cfg.Metrics.Enabled {
servMux.Handle("/metrics", WrapHandlerInBasicAuth(promhttp.Handler(), cfg.Metrics.BasicAuth))
}
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
// responses.
// Handles OPTIONS requests directly.

95
common/httpapi_test.go Normal file
View 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)
}
})
}
}

View file

@ -53,6 +53,15 @@ media:
height: 600
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
turn:
# Whether or not guests can request TURN credentials