From a1b7e4ef3f9231ce6933cd2f5e7e93525e840293 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Tue, 23 Mar 2021 11:33:36 +0000 Subject: [PATCH 01/14] log less for failed key querys, add counters for incoming pdus/edus (#1801) * log less for failed key querys, add counters for incoming pdus/edus * use labels * Blacklist flakey test * Fix metrics --- federationapi/routing/send.go | 28 ++++++++++++++++++++++++ federationsender/consumers/eduserver.go | 3 +-- keyserver/internal/device_list_update.go | 14 +++++++----- sytest-blacklist | 5 ++++- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index ea0b54b61..d43ed8324 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -30,9 +30,34 @@ import ( "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" + "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" ) +var ( + pduCountTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "dendrite", + Subsystem: "federationapi", + Name: "recv_pdus", + }, + []string{"status"}, + ) + eduCountTotal = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "dendrite", + Subsystem: "federationapi", + Name: "recv_edus", + }, + ) +) + +func init() { + prometheus.MustRegister( + pduCountTotal, eduCountTotal, + ) +} + // Send implements /_matrix/federation/v1/send/{txnID} func Send( httpReq *http.Request, @@ -133,6 +158,7 @@ func (t *txnReq) processTransaction(ctx context.Context) (*gomatrixserverlib.Res pdus := []*gomatrixserverlib.HeaderedEvent{} for _, pdu := range t.PDUs { + pduCountTotal.WithLabelValues("total").Inc() var header struct { RoomID string `json:"room_id"` } @@ -224,6 +250,7 @@ func (t *txnReq) processTransaction(ctx context.Context) (*gomatrixserverlib.Res } } else { results[e.EventID()] = gomatrixserverlib.PDUResult{} + pduCountTotal.WithLabelValues("success").Inc() } } @@ -281,6 +308,7 @@ func (t *txnReq) haveEventIDs() map[string]bool { func (t *txnReq) processEDUs(ctx context.Context) { for _, e := range t.EDUs { + eduCountTotal.Inc() switch e.Type { case gomatrixserverlib.MTyping: // https://matrix.org/docs/spec/server_server/latest#typing-notifications diff --git a/federationsender/consumers/eduserver.go b/federationsender/consumers/eduserver.go index 639cd7315..9a1ec1e26 100644 --- a/federationsender/consumers/eduserver.go +++ b/federationsender/consumers/eduserver.go @@ -212,8 +212,7 @@ func (t *OutputEDUConsumer) onReceiptEvent(msg *sarama.ConsumerMessage) error { return nil } if receiptServerName != t.ServerName { - log.WithField("other_server", receiptServerName).Info("Suppressing receipt notif: originated elsewhere") - return nil + return nil // don't log, very spammy as it logs for each remote receipt } joined, err := t.db.GetJoinedHosts(context.TODO(), receipt.RoomID) diff --git a/keyserver/internal/device_list_update.go b/keyserver/internal/device_list_update.go index 7f6a14f49..1a4d9818a 100644 --- a/keyserver/internal/device_list_update.go +++ b/keyserver/internal/device_list_update.go @@ -330,16 +330,16 @@ func (u *DeviceListUpdater) processServer(serverName gomatrixserverlib.ServerNam logger.WithError(err).Error("failed to load stale device lists") return waitTime, true } - hasFailures := false + failCount := 0 for _, userID := range userIDs { if ctx.Err() != nil { // we've timed out, give up and go to the back of the queue to let another server be processed. - hasFailures = true + failCount += 1 break } res, err := u.fedClient.GetUserDevices(ctx, serverName, userID) if err != nil { - logger.WithError(err).WithField("user_id", userID).Error("failed to query device keys for user") + failCount += 1 fcerr, ok := err.(*fedsenderapi.FederationClientError) if ok { if fcerr.RetryAfter > 0 { @@ -351,20 +351,22 @@ func (u *DeviceListUpdater) processServer(serverName gomatrixserverlib.ServerNam waitTime = time.Hour logger.WithError(err).Warn("GetUserDevices returned unknown error type") } - hasFailures = true continue } err = u.updateDeviceList(&res) if err != nil { logger.WithError(err).WithField("user_id", userID).Error("fetched device list but failed to store/emit it") - hasFailures = true + failCount += 1 } } + if failCount > 0 { + logger.WithField("total", len(userIDs)).WithField("failed", failCount).Error("failed to query device keys for some users") + } for _, userID := range userIDs { // always clear the channel to unblock Update calls regardless of success/failure u.clearChannel(userID) } - return waitTime, hasFailures + return waitTime, failCount > 0 } func (u *DeviceListUpdater) updateDeviceList(res *gomatrixserverlib.RespUserDevices) error { diff --git a/sytest-blacklist b/sytest-blacklist index b635c9f0e..4d9587d00 100644 --- a/sytest-blacklist +++ b/sytest-blacklist @@ -69,4 +69,7 @@ Forgotten room messages cannot be paginated Can re-join room if re-invited # Blacklisted due to flakiness after #1774 -Local device key changes get to remote servers with correct prev_id \ No newline at end of file +Local device key changes get to remote servers with correct prev_id + +# Flakey +Local device key changes appear in /keys/changes \ No newline at end of file From 802f1c96f804f7a146e4e12e25b20c980a6af870 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Tue, 23 Mar 2021 15:22:00 +0000 Subject: [PATCH 02/14] Add more metrics (#1802) * Add more metrics * Linting --- federationapi/routing/send.go | 55 +++++++++++++++++++++--- keyserver/internal/device_list_update.go | 20 +++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index d43ed8324..1dd874d48 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -34,27 +34,55 @@ import ( "github.com/sirupsen/logrus" ) +const ( + // Event was passed to the roomserver + MetricsOutcomeOK = "ok" + // Event failed to be processed + MetricsOutcomeFail = "fail" + // Event failed auth checks + MetricsOutcomeRejected = "rejected" + // Terminated the transaction + MetricsOutcomeFatal = "fatal" + // The event has missing auth_events we need to fetch + MetricsWorkMissingAuthEvents = "missing_auth_events" + // No work had to be done as we had all prev/auth events + MetricsWorkDirect = "direct" + // The event has missing prev_events we need to call /g_m_e for + MetricsWorkMissingPrevEvents = "missing_prev_events" +) + var ( pduCountTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: "dendrite", Subsystem: "federationapi", Name: "recv_pdus", + Help: "Number of incoming PDUs from remote servers with labels for success", }, - []string{"status"}, + []string{"status"}, // 'success' or 'total' ) eduCountTotal = prometheus.NewCounter( prometheus.CounterOpts{ Namespace: "dendrite", Subsystem: "federationapi", Name: "recv_edus", + Help: "Number of incoming EDUs from remote servers", }, ) + processEventSummary = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Namespace: "dendrite", + Subsystem: "federationapi", + Name: "process_event", + Help: "How long it takes to process an incoming event and what work had to be done for it", + }, + []string{"work", "outcome"}, + ) ) func init() { prometheus.MustRegister( - pduCountTotal, eduCountTotal, + pduCountTotal, eduCountTotal, processEventSummary, ) } @@ -140,6 +168,7 @@ type txnReq struct { // new events which the roomserver does not know about newEvents map[string]bool newEventsMutex sync.RWMutex + work string // metrics } // A subset of FederationClient functionality that txn requires. Useful for testing. @@ -212,6 +241,7 @@ func (t *txnReq) processTransaction(ctx context.Context) (*gomatrixserverlib.Res // Process the events. for _, e := range pdus { + evStart := time.Now() if err := t.processEvent(ctx, e.Unwrap()); err != nil { // If the error is due to the event itself being bad then we skip // it and move onto the next event. We report an error so that the @@ -233,17 +263,25 @@ func (t *txnReq) processTransaction(ctx context.Context) (*gomatrixserverlib.Res // our server so we should bail processing the transaction entirely. util.GetLogger(ctx).Warnf("Processing %s failed fatally: %s", e.EventID(), err) jsonErr := util.ErrorResponse(err) + processEventSummary.WithLabelValues(t.work, MetricsOutcomeFatal).Observe( + float64(time.Since(evStart).Nanoseconds()) / 1000., + ) return nil, &jsonErr } else { // Auth errors mean the event is 'rejected' which have to be silent to appease sytest errMsg := "" + outcome := MetricsOutcomeRejected _, rejected := err.(*gomatrixserverlib.NotAllowed) if !rejected { errMsg = err.Error() + outcome = MetricsOutcomeFail } util.GetLogger(ctx).WithError(err).WithField("event_id", e.EventID()).WithField("rejected", rejected).Warn( "Failed to process incoming federation event, skipping", ) + processEventSummary.WithLabelValues(t.work, outcome).Observe( + float64(time.Since(evStart).Nanoseconds()) / 1000., + ) results[e.EventID()] = gomatrixserverlib.PDUResult{ Error: errMsg, } @@ -251,6 +289,9 @@ func (t *txnReq) processTransaction(ctx context.Context) (*gomatrixserverlib.Res } else { results[e.EventID()] = gomatrixserverlib.PDUResult{} pduCountTotal.WithLabelValues("success").Inc() + processEventSummary.WithLabelValues(t.work, MetricsOutcomeOK).Observe( + float64(time.Since(evStart).Nanoseconds()) / 1000., + ) } } @@ -452,6 +493,7 @@ func (t *txnReq) getServers(ctx context.Context, roomID string) []gomatrixserver func (t *txnReq) processEvent(ctx context.Context, e *gomatrixserverlib.Event) error { logger := util.GetLogger(ctx).WithField("event_id", e.EventID()).WithField("room_id", e.RoomID()) + t.work = "" // reset from previous event // Work out if the roomserver knows everything it needs to know to auth // the event. This includes the prev_events and auth_events. @@ -480,6 +522,7 @@ func (t *txnReq) processEvent(ctx context.Context, e *gomatrixserverlib.Event) e } if len(stateResp.MissingAuthEventIDs) > 0 { + t.work = MetricsWorkMissingAuthEvents logger.Infof("Event refers to %d unknown auth_events", len(stateResp.MissingAuthEventIDs)) if err := t.retrieveMissingAuthEvents(ctx, e, &stateResp); err != nil { return fmt.Errorf("t.retrieveMissingAuthEvents: %w", err) @@ -487,9 +530,11 @@ func (t *txnReq) processEvent(ctx context.Context, e *gomatrixserverlib.Event) e } if len(stateResp.MissingPrevEventIDs) > 0 { + t.work = MetricsWorkMissingPrevEvents logger.Infof("Event refers to %d unknown prev_events", len(stateResp.MissingPrevEventIDs)) return t.processEventWithMissingState(ctx, e, stateResp.RoomVersion) } + t.work = MetricsWorkDirect // pass the event to the roomserver which will do auth checks // If the event fail auth checks, gmsl.NotAllowed error will be returned which we be silently @@ -784,7 +829,7 @@ func (t *txnReq) lookupStateAfterEventLocally(ctx context.Context, roomID, event queryReq := api.QueryEventsByIDRequest{ EventIDs: missingEventList, } - util.GetLogger(ctx).Infof("Fetching missing auth events: %v", missingEventList) + util.GetLogger(ctx).WithField("count", len(missingEventList)).Infof("Fetching missing auth events") var queryRes api.QueryEventsByIDResponse if err = t.rsAPI.QueryEventsByID(ctx, &queryReq, &queryRes); err != nil { return nil @@ -854,10 +899,6 @@ retryAllowedState: }, nil } -// getMissingEvents returns a nil backwardsExtremity if missing events were fetched and handled, else returns the new backwards extremity which we should -// begin from. Returns an error only if we should terminate the transaction which initiated /get_missing_events -// This function recursively calls txnReq.processEvent with the missing events, which will be processed before this function returns. -// This means that we may recursively call this function, as we spider back up prev_events. func (t *txnReq) getMissingEvents(ctx context.Context, e *gomatrixserverlib.Event, roomVersion gomatrixserverlib.RoomVersion) (newEvents []*gomatrixserverlib.Event, err error) { logger := util.GetLogger(ctx).WithField("event_id", e.EventID()).WithField("room_id", e.RoomID()) needed := gomatrixserverlib.StateNeededForAuth([]*gomatrixserverlib.Event{e}) diff --git a/keyserver/internal/device_list_update.go b/keyserver/internal/device_list_update.go index 1a4d9818a..bd563ef35 100644 --- a/keyserver/internal/device_list_update.go +++ b/keyserver/internal/device_list_update.go @@ -26,9 +26,28 @@ import ( "github.com/matrix-org/dendrite/keyserver/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" + "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" ) +var ( + deviceListUpdateCount = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "dendrite", + Subsystem: "keyserver", + Name: "device_list_update", + Help: "Number of times we have attempted to update device lists from this server", + }, + []string{"server"}, + ) +) + +func init() { + prometheus.MustRegister( + deviceListUpdateCount, + ) +} + // DeviceListUpdater handles device list updates from remote servers. // // In the case where we have the prev_id for an update, the updater just stores the update (after acquiring a per-user lock). @@ -319,6 +338,7 @@ func (u *DeviceListUpdater) worker(ch chan gomatrixserverlib.ServerName) { } func (u *DeviceListUpdater) processServer(serverName gomatrixserverlib.ServerName) (time.Duration, bool) { + deviceListUpdateCount.WithLabelValues(string(serverName)).Inc() requestTimeout := time.Second * 30 // max amount of time we want to spend on each request ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) defer cancel() From af41f6d4549759afd7f52f780b40abe2834ab4c0 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Wed, 24 Mar 2021 10:25:24 +0000 Subject: [PATCH 03/14] Add Sentry support (#1803) * Add Sentry support * Use HTTP Sentry properly maybe * Capture panics * Log fed Sentry stuff correctly * British english linter --- federationapi/routing/send.go | 2 + go.mod | 1 + go.sum | 79 +++++++++++++++++++++ internal/httputil/httpapi.go | 50 ++++++++++++- roomserver/internal/api.go | 3 + roomserver/internal/input/input.go | 3 + roomserver/internal/perform/perform_join.go | 2 + setup/base.go | 42 ++++++++++- setup/config/config_global.go | 23 ++++++ syncapi/consumers/clientapi.go | 3 + syncapi/consumers/eduserver_receipts.go | 3 + syncapi/consumers/eduserver_sendtodevice.go | 4 ++ syncapi/consumers/eduserver_typing.go | 2 + syncapi/consumers/keychange.go | 3 + syncapi/consumers/roomserver.go | 8 +++ 15 files changed, 223 insertions(+), 5 deletions(-) diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index 1dd874d48..231a16863 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -23,6 +23,7 @@ import ( "sync" "time" + "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/clientapi/jsonerror" eduserverAPI "github.com/matrix-org/dendrite/eduserver/api" keyapi "github.com/matrix-org/dendrite/keyserver/api" @@ -259,6 +260,7 @@ func (t *txnReq) processTransaction(ctx context.Context) (*gomatrixserverlib.Res // If we bail and stop processing then we risk wedging incoming // transactions from that server forever. if isProcessingErrorFatal(err) { + sentry.CaptureException(err) // Any other error should be the result of a temporary error in // our server so we should bail processing the transaction entirely. util.GetLogger(ctx).Warnf("Processing %s failed fatally: %s", e.EventID(), err) diff --git a/go.mod b/go.mod index e79a0fca4..a3d80f1b1 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/HdrHistogram/hdrhistogram-go v1.0.1 // indirect github.com/Shopify/sarama v1.28.0 + github.com/getsentry/sentry-go v0.10.0 github.com/gologme/log v1.2.0 github.com/gorilla/mux v1.8.0 github.com/hashicorp/golang-lru v0.5.4 diff --git a/go.sum b/go.sum index 8bfeeee38..90b5527c8 100644 --- a/go.sum +++ b/go.sum @@ -13,14 +13,18 @@ github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 h1:p3puK8Sl2xK+2Fnn github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/HdrHistogram/hdrhistogram-go v1.0.1 h1:GX8GAYDuhlFQnI2fRDHQhTlkHMz8bEn0jTI6LJU0mpw= github.com/HdrHistogram/hdrhistogram-go v1.0.1/go.mod h1:BWJ+nMSHY3L41Zj7CA3uXnloDp7xxV0YvstAE7nKTaM= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= github.com/Shopify/sarama v1.28.0 h1:lOi3SfE6OcFlW9Trgtked2aHNZ2BIG/d6Do+PEUAqqM= @@ -31,6 +35,7 @@ github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmx github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -48,6 +53,7 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.0.2/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= @@ -86,6 +92,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -122,12 +129,16 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 h1:u/UEqS66A5ckRmS4yNpjmVH56sVtS/RfclBAYocb4as= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= @@ -143,9 +154,15 @@ github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebP github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g= +github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -153,8 +170,12 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -191,6 +212,7 @@ github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -273,6 +295,7 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -318,6 +341,11 @@ github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBW github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.1.1 h1:G4TtqN+V9y9HY9TA6BwbCVyyBZ2B9MbCjR2MtGx8FR0= github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= +github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -354,20 +382,30 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= +github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= +github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= +github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= +github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= +github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -385,6 +423,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= @@ -637,17 +677,22 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.2/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= @@ -673,6 +718,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -756,6 +802,7 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= @@ -788,6 +835,8 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A= github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -837,7 +886,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= @@ -923,9 +974,17 @@ github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24sz github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ= github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= @@ -944,10 +1003,17 @@ github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1: github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20210218094457-e77ca8019daa h1:YHeZ1KN4KmuAjqmBSan1JtwyoPQoklzMjMqIbaS5Ywo= github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20210218094457-e77ca8019daa/go.mod h1:G716RAw9WTLbLFI7lVj1GKTU16wb9MYl6iE9j4JlWeI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= @@ -998,6 +1064,7 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1034,12 +1101,16 @@ golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1084,6 +1155,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1122,10 +1194,12 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1195,11 +1269,14 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/h2non/bimg.v1 v1.1.5 h1:w0KZvuIj2FQR0GedFZJNbISxk+oO5tDpkSLi9nuIrfQ= gopkg.in/h2non/bimg.v1 v1.1.5/go.mod h1:PgsZL7dLwUbsGm1NYps320GxGgvQNTnecMCZqxV11So= gopkg.in/h2non/gock.v1 v1.0.14 h1:fTeu9fcUvSnLNacYvYI54h+1/XEteDyHvrVCZEEEYNM= gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= @@ -1207,6 +1284,7 @@ gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/macaroon.v2 v2.1.0 h1:HZcsjBCzq9t0eBPMKqTN/uSN6JOm78ZJ2INbqcBQOUI= gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= @@ -1222,6 +1300,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index c69468e6c..704bdecb6 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -16,6 +16,7 @@ package httputil import ( "context" + "fmt" "io" "net/http" "net/http/httptest" @@ -25,6 +26,7 @@ import ( "sync" "time" + "github.com/getsentry/sentry-go" "github.com/gorilla/mux" "github.com/matrix-org/dendrite/clientapi/auth" federationsenderAPI "github.com/matrix-org/dendrite/federationsender/api" @@ -59,8 +61,29 @@ func MakeAuthAPI( logger := util.GetLogger((req.Context())) logger = logger.WithField("user_id", device.UserID) req = req.WithContext(util.ContextWithLogger(req.Context(), logger)) + // add the user to Sentry, if enabled + hub := sentry.GetHubFromContext(req.Context()) + if hub != nil { + hub.Scope().SetTag("user_id", device.UserID) + hub.Scope().SetTag("device_id", device.ID) + } + defer func() { + if r := recover(); r != nil { + if hub != nil { + hub.CaptureException(fmt.Errorf("%s panicked", req.URL.Path)) + } + // re-panic to return the 500 + panic(r) + } + }() - return f(req, device) + jsonRes := f(req, device) + // do not log 4xx as errors as they are client fails, not server fails + if hub != nil && jsonRes.Code >= 500 { + hub.Scope().SetExtra("response", jsonRes) + hub.CaptureException(fmt.Errorf("%s returned HTTP %d", req.URL.Path, jsonRes.Code)) + } + return jsonRes } return MakeExternalAPI(metricsName, h) } @@ -195,13 +218,34 @@ func MakeFedAPI( if fedReq == nil { return errResp } + // add the user to Sentry, if enabled + hub := sentry.GetHubFromContext(req.Context()) + if hub != nil { + hub.Scope().SetTag("origin", string(fedReq.Origin())) + hub.Scope().SetTag("uri", fedReq.RequestURI()) + } + defer func() { + if r := recover(); r != nil { + if hub != nil { + hub.CaptureException(fmt.Errorf("%s panicked", req.URL.Path)) + } + // re-panic to return the 500 + panic(r) + } + }() go wakeup.Wakeup(req.Context(), fedReq.Origin()) vars, err := URLDecodeMapValues(mux.Vars(req)) if err != nil { - return util.ErrorResponse(err) + return util.MatrixErrorResponse(400, "M_UNRECOGNISED", "badly encoded query params") } - return f(req, fedReq, vars) + jsonRes := f(req, fedReq, vars) + // do not log 4xx as errors as they are client fails, not server fails + if hub != nil && jsonRes.Code >= 500 { + hub.Scope().SetExtra("response", jsonRes) + hub.CaptureException(fmt.Errorf("%s returned HTTP %d", req.URL.Path, jsonRes.Code)) + } + return jsonRes } return MakeExternalAPI(metricsName, h) } diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go index 5b8959b0f..c9f92f9ff 100644 --- a/roomserver/internal/api.go +++ b/roomserver/internal/api.go @@ -4,6 +4,7 @@ import ( "context" "github.com/Shopify/sarama" + "github.com/getsentry/sentry-go" asAPI "github.com/matrix-org/dendrite/appservice/api" fsAPI "github.com/matrix-org/dendrite/federationsender/api" "github.com/matrix-org/dendrite/internal/caching" @@ -145,6 +146,7 @@ func (r *RoomserverInternalAPI) PerformInvite( ) error { outputEvents, err := r.Inviter.PerformInvite(ctx, req, res) if err != nil { + sentry.CaptureException(err) return err } if len(outputEvents) == 0 { @@ -160,6 +162,7 @@ func (r *RoomserverInternalAPI) PerformLeave( ) error { outputEvents, err := r.Leaver.PerformLeave(ctx, req, res) if err != nil { + sentry.CaptureException(err) return err } if len(outputEvents) == 0 { diff --git a/roomserver/internal/input/input.go b/roomserver/internal/input/input.go index 404bc7423..82ece2307 100644 --- a/roomserver/internal/input/input.go +++ b/roomserver/internal/input/input.go @@ -22,6 +22,7 @@ import ( "time" "github.com/Shopify/sarama" + "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/internal/hooks" "github.com/matrix-org/dendrite/roomserver/acls" "github.com/matrix-org/dendrite/roomserver/api" @@ -64,6 +65,8 @@ func (w *inputWorker) start() { _, task.err = w.r.processRoomEvent(task.ctx, task.event) if task.err == nil { hooks.Run(hooks.KindNewEventPersisted, task.event.Event) + } else { + sentry.CaptureException(task.err) } task.wg.Done() case <-time.After(time.Second * 5): diff --git a/roomserver/internal/perform/perform_join.go b/roomserver/internal/perform/perform_join.go index 6eaaec230..048496d45 100644 --- a/roomserver/internal/perform/perform_join.go +++ b/roomserver/internal/perform/perform_join.go @@ -21,6 +21,7 @@ import ( "strings" "time" + "github.com/getsentry/sentry-go" fsAPI "github.com/matrix-org/dendrite/federationsender/api" "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/roomserver/api" @@ -51,6 +52,7 @@ func (r *Joiner) PerformJoin( ) { roomID, joinedVia, err := r.performJoin(ctx, req) if err != nil { + sentry.CaptureException(err) perr, ok := err.(*api.PerformError) if ok { res.Error = perr diff --git a/setup/base.go b/setup/base.go index 4c50a6be6..b081ffaf8 100644 --- a/setup/base.go +++ b/setup/base.go @@ -27,6 +27,8 @@ import ( "syscall" "time" + "github.com/getsentry/sentry-go" + sentryhttp "github.com/getsentry/sentry-go/http" "github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/gomatrixserverlib" @@ -114,6 +116,21 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, useHTTPAPIs boo logrus.WithError(err).Panicf("failed to start opentracing") } + if cfg.Global.Sentry.Enabled { + logrus.Info("Setting up Sentry for debugging...") + err = sentry.Init(sentry.ClientOptions{ + Dsn: cfg.Global.Sentry.DSN, + Environment: cfg.Global.Sentry.Environment, + Debug: true, + ServerName: string(cfg.Global.ServerName), + Release: "dendrite@" + internal.VersionString(), + AttachStacktrace: true, + }) + if err != nil { + logrus.WithError(err).Panic("failed to start Sentry") + } + } + cache, err := caching.NewInMemoryLRUCache(true) if err != nil { logrus.WithError(err).Warnf("Failed to create cache") @@ -353,10 +370,26 @@ func (b *BaseDendrite) SetupAndServeHTTP( internalRouter.Handle("/metrics", httputil.WrapHandlerInBasicAuth(promhttp.Handler(), b.Cfg.Global.Metrics.BasicAuth)) } - externalRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(b.PublicClientAPIMux) + var clientHandler http.Handler + clientHandler = b.PublicClientAPIMux + if b.Cfg.Global.Sentry.Enabled { + sentryHandler := sentryhttp.New(sentryhttp.Options{ + Repanic: true, + }) + clientHandler = sentryHandler.Handle(b.PublicClientAPIMux) + } + var federationHandler http.Handler + federationHandler = b.PublicFederationAPIMux + if b.Cfg.Global.Sentry.Enabled { + sentryHandler := sentryhttp.New(sentryhttp.Options{ + Repanic: true, + }) + federationHandler = sentryHandler.Handle(b.PublicFederationAPIMux) + } + externalRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(clientHandler) if !b.Cfg.Global.DisableFederation { externalRouter.PathPrefix(httputil.PublicKeyPathPrefix).Handler(b.PublicKeyAPIMux) - externalRouter.PathPrefix(httputil.PublicFederationPathPrefix).Handler(b.PublicFederationAPIMux) + externalRouter.PathPrefix(httputil.PublicFederationPathPrefix).Handler(federationHandler) } externalRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(b.PublicMediaAPIMux) @@ -436,6 +469,11 @@ func (b *BaseDendrite) WaitForShutdown() { b.ProcessContext.ShutdownDendrite() b.ProcessContext.WaitForComponentsToFinish() + if b.Cfg.Global.Sentry.Enabled { + if !sentry.Flush(time.Second * 5) { + logrus.Warnf("failed to flush all Sentry events!") + } + } logrus.Warnf("Dendrite is exiting now") } diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 4b5297ff6..90a92f2bc 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -49,6 +49,9 @@ type Global struct { // Metrics configuration Metrics Metrics `yaml:"metrics"` + // Sentry configuration + Sentry Sentry `yaml:"sentry"` + // DNS caching options for all outbound HTTP requests DNSCache DNSCacheOptions `yaml:"dns_cache"` } @@ -63,6 +66,7 @@ func (c *Global) Defaults() { c.Kafka.Defaults() c.Metrics.Defaults() c.DNSCache.Defaults() + c.Sentry.Defaults() } func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) { @@ -71,6 +75,7 @@ func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) { c.Kafka.Verify(configErrs, isMonolith) c.Metrics.Verify(configErrs, isMonolith) + c.Sentry.Verify(configErrs, isMonolith) c.DNSCache.Verify(configErrs, isMonolith) } @@ -111,6 +116,24 @@ func (c *Metrics) Defaults() { func (c *Metrics) Verify(configErrs *ConfigErrors, isMonolith bool) { } +// The configuration to use for Sentry error reporting +type Sentry struct { + Enabled bool `yaml:"enabled"` + // The DSN to connect to e.g "https://examplePublicKey@o0.ingest.sentry.io/0" + // See https://docs.sentry.io/platforms/go/configuration/options/ + DSN string `yaml:"dsn"` + // The environment e.g "production" + // See https://docs.sentry.io/platforms/go/configuration/environments/ + Environment string `yaml:"environment"` +} + +func (c *Sentry) Defaults() { + c.Enabled = false +} + +func (c *Sentry) Verify(configErrs *ConfigErrors, isMonolith bool) { +} + type DatabaseOptions struct { // The connection string, file:filename.db or postgres://server.... ConnectionString DataSource `yaml:"connection_string"` diff --git a/syncapi/consumers/clientapi.go b/syncapi/consumers/clientapi.go index 8dab513c3..a166ae14d 100644 --- a/syncapi/consumers/clientapi.go +++ b/syncapi/consumers/clientapi.go @@ -19,6 +19,7 @@ import ( "encoding/json" "github.com/Shopify/sarama" + "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/setup/config" @@ -78,6 +79,7 @@ func (s *OutputClientDataConsumer) onMessage(msg *sarama.ConsumerMessage) error if err := json.Unmarshal(msg.Value, &output); err != nil { // If the message was invalid, log it and move on to the next message in the stream log.WithError(err).Errorf("client API server output log: message parse failure") + sentry.CaptureException(err) return nil } @@ -90,6 +92,7 @@ func (s *OutputClientDataConsumer) onMessage(msg *sarama.ConsumerMessage) error context.TODO(), string(msg.Key), output.RoomID, output.Type, ) if err != nil { + sentry.CaptureException(err) log.WithFields(log.Fields{ "type": output.Type, "room_id": output.RoomID, diff --git a/syncapi/consumers/eduserver_receipts.go b/syncapi/consumers/eduserver_receipts.go index 598908070..668f945bc 100644 --- a/syncapi/consumers/eduserver_receipts.go +++ b/syncapi/consumers/eduserver_receipts.go @@ -19,6 +19,7 @@ import ( "encoding/json" "github.com/Shopify/sarama" + "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/eduserver/api" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/setup/config" @@ -78,6 +79,7 @@ func (s *OutputReceiptEventConsumer) onMessage(msg *sarama.ConsumerMessage) erro if err := json.Unmarshal(msg.Value, &output); err != nil { // If the message was invalid, log it and move on to the next message in the stream log.WithError(err).Errorf("EDU server output log: message parse failure") + sentry.CaptureException(err) return nil } @@ -90,6 +92,7 @@ func (s *OutputReceiptEventConsumer) onMessage(msg *sarama.ConsumerMessage) erro output.Timestamp, ) if err != nil { + sentry.CaptureException(err) return err } diff --git a/syncapi/consumers/eduserver_sendtodevice.go b/syncapi/consumers/eduserver_sendtodevice.go index 668d30784..5e626aefe 100644 --- a/syncapi/consumers/eduserver_sendtodevice.go +++ b/syncapi/consumers/eduserver_sendtodevice.go @@ -19,6 +19,7 @@ import ( "encoding/json" "github.com/Shopify/sarama" + "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/eduserver/api" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/setup/config" @@ -82,11 +83,13 @@ func (s *OutputSendToDeviceEventConsumer) onMessage(msg *sarama.ConsumerMessage) if err := json.Unmarshal(msg.Value, &output); err != nil { // If the message was invalid, log it and move on to the next message in the stream log.WithError(err).Errorf("EDU server output log: message parse failure") + sentry.CaptureException(err) return err } _, domain, err := gomatrixserverlib.SplitID('@', output.UserID) if err != nil { + sentry.CaptureException(err) return err } if domain != s.serverName { @@ -104,6 +107,7 @@ func (s *OutputSendToDeviceEventConsumer) onMessage(msg *sarama.ConsumerMessage) context.TODO(), output.UserID, output.DeviceID, output.SendToDeviceEvent, ) if err != nil { + sentry.CaptureException(err) log.WithError(err).Errorf("failed to store send-to-device message") return err } diff --git a/syncapi/consumers/eduserver_typing.go b/syncapi/consumers/eduserver_typing.go index 7d7ab3bfb..c10d1e94e 100644 --- a/syncapi/consumers/eduserver_typing.go +++ b/syncapi/consumers/eduserver_typing.go @@ -18,6 +18,7 @@ import ( "encoding/json" "github.com/Shopify/sarama" + "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/eduserver/api" "github.com/matrix-org/dendrite/eduserver/cache" "github.com/matrix-org/dendrite/internal" @@ -83,6 +84,7 @@ func (s *OutputTypingEventConsumer) onMessage(msg *sarama.ConsumerMessage) error if err := json.Unmarshal(msg.Value, &output); err != nil { // If the message was invalid, log it and move on to the next message in the stream log.WithError(err).Errorf("EDU server output log: message parse failure") + sentry.CaptureException(err) return nil } diff --git a/syncapi/consumers/keychange.go b/syncapi/consumers/keychange.go index 0e1a790d0..0d2ecd449 100644 --- a/syncapi/consumers/keychange.go +++ b/syncapi/consumers/keychange.go @@ -20,6 +20,7 @@ import ( "sync" "github.com/Shopify/sarama" + "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/keyserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" @@ -107,6 +108,7 @@ func (s *OutputKeyChangeEventConsumer) onMessage(msg *sarama.ConsumerMessage) er if err := json.Unmarshal(msg.Value, &output); err != nil { // If the message was invalid, log it and move on to the next message in the stream log.WithError(err).Error("syncapi: failed to unmarshal key change event from key server") + sentry.CaptureException(err) return err } // work out who we need to notify about the new key @@ -116,6 +118,7 @@ func (s *OutputKeyChangeEventConsumer) onMessage(msg *sarama.ConsumerMessage) er }, &queryRes) if err != nil { log.WithError(err).Error("syncapi: failed to QuerySharedUsers for key change event from key server") + sentry.CaptureException(err) return err } // make sure we get our own key updates too! diff --git a/syncapi/consumers/roomserver.go b/syncapi/consumers/roomserver.go index 85e73df62..a6aeee3a8 100644 --- a/syncapi/consumers/roomserver.go +++ b/syncapi/consumers/roomserver.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/Shopify/sarama" + "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" @@ -148,18 +149,21 @@ func (s *OutputRoomEventConsumer) onNewRoomEvent( ev, err := s.updateStateEvent(ev) if err != nil { + sentry.CaptureException(err) return err } for i := range addsStateEvents { addsStateEvents[i], err = s.updateStateEvent(addsStateEvents[i]) if err != nil { + sentry.CaptureException(err) return err } } if msg.RewritesState { if err = s.db.PurgeRoomState(ctx, ev.RoomID()); err != nil { + sentry.CaptureException(err) return fmt.Errorf("s.db.PurgeRoom: %w", err) } } @@ -187,6 +191,7 @@ func (s *OutputRoomEventConsumer) onNewRoomEvent( if pduPos, err = s.notifyJoinedPeeks(ctx, ev, pduPos); err != nil { log.WithError(err).Errorf("Failed to notifyJoinedPeeks for PDU pos %d", pduPos) + sentry.CaptureException(err) return err } @@ -279,6 +284,7 @@ func (s *OutputRoomEventConsumer) onNewInviteEvent( } pduPos, err := s.db.AddInviteEvent(ctx, msg.Event) if err != nil { + sentry.CaptureException(err) // panic rather than continue with an inconsistent database log.WithFields(log.Fields{ "event_id": msg.Event.EventID(), @@ -300,6 +306,7 @@ func (s *OutputRoomEventConsumer) onRetireInviteEvent( ) error { pduPos, err := s.db.RetireInviteEvent(ctx, msg.EventID) if err != nil { + sentry.CaptureException(err) // panic rather than continue with an inconsistent database log.WithFields(log.Fields{ "event_id": msg.EventID, @@ -320,6 +327,7 @@ func (s *OutputRoomEventConsumer) onNewPeek( ) error { sp, err := s.db.AddPeek(ctx, msg.RoomID, msg.UserID, msg.DeviceID) if err != nil { + sentry.CaptureException(err) // panic rather than continue with an inconsistent database log.WithFields(log.Fields{ log.ErrorKey: err, From 0ee1c56ffd6910de9bd268df0333c0135c42bff3 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 30 Mar 2021 03:53:02 -0500 Subject: [PATCH 04/14] Use log directory that we will be able to write to (#1799) Fix https://github.com/matrix-org/dendrite/issues/1644 --- .gitignore | 1 + dendrite-config.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 34c5b6805..c5bf92ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ /_test /vendor/bin /docker/build +/logs # Architecture specific extensions/prefixes *.[568vq] diff --git a/dendrite-config.yaml b/dendrite-config.yaml index 13564590d..402987f98 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -383,4 +383,4 @@ logging: - type: file level: info params: - path: /var/log/dendrite + path: ./logs From f8d3a762c49a1dafe4e484a2440ade2bb6ba32ac Mon Sep 17 00:00:00 2001 From: Kegsay Date: Tue, 30 Mar 2021 10:01:32 +0100 Subject: [PATCH 05/14] Add a per-room mutex to federationapi when processing transactions (#1810) * Add a per-room mutex to federationapi when processing transactions This has numerous benefits: - Prevents us doing lots of state resolutions in busy rooms. Previously, room forks would always result in a state resolution being performed immediately, without checking if we were already doing this in a different transaction. Now they will queue up, resulting in fewer calls to `/state_ids`, `/g_m_e`, etc. - Prevents memory usage from growing too large as a result and potentially OOMing. And costs: - High traffic rooms will be slightly slower due to head-of-line blocking from other servers, though this has always been an issue as roomserver has a per-room mutex already. * Fix unit tests * Correct mutex lock ordering --- federationapi/routing/routing.go | 4 +++- federationapi/routing/send.go | 6 +++++ federationapi/routing/send_test.go | 2 ++ internal/mutex.go | 38 ++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 internal/mutex.go diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index b579ae1fa..ce018904f 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -21,6 +21,7 @@ import ( "github.com/matrix-org/dendrite/clientapi/jsonerror" eduserverAPI "github.com/matrix-org/dendrite/eduserver/api" federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api" + "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal/httputil" keyserverAPI "github.com/matrix-org/dendrite/keyserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" @@ -92,12 +93,13 @@ func Setup( v2keysmux.Handle("/query", notaryKeys).Methods(http.MethodPost) v2keysmux.Handle("/query/{serverName}/{keyID}", notaryKeys).Methods(http.MethodGet) + mu := internal.NewMutexByRoom() v1fedmux.Handle("/send/{txnID}", httputil.MakeFedAPI( "federation_send", cfg.Matrix.ServerName, keys, wakeup, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse { return Send( httpReq, request, gomatrixserverlib.TransactionID(vars["txnID"]), - cfg, rsAPI, eduAPI, keyAPI, keys, federation, + cfg, rsAPI, eduAPI, keyAPI, keys, federation, mu, ) }, )).Methods(http.MethodPut, http.MethodOptions) diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index 231a16863..b48d6c0b8 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -26,6 +26,7 @@ import ( "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/clientapi/jsonerror" eduserverAPI "github.com/matrix-org/dendrite/eduserver/api" + "github.com/matrix-org/dendrite/internal" keyapi "github.com/matrix-org/dendrite/keyserver/api" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" @@ -98,6 +99,7 @@ func Send( keyAPI keyapi.KeyInternalAPI, keys gomatrixserverlib.JSONVerifier, federation *gomatrixserverlib.FederationClient, + mu *internal.MutexByRoom, ) util.JSONResponse { t := txnReq{ rsAPI: rsAPI, @@ -107,6 +109,7 @@ func Send( haveEvents: make(map[string]*gomatrixserverlib.HeaderedEvent), newEvents: make(map[string]bool), keyAPI: keyAPI, + roomsMu: mu, } var txnEvents struct { @@ -163,6 +166,7 @@ type txnReq struct { federation txnFederationClient servers []gomatrixserverlib.ServerName serversMutex sync.RWMutex + roomsMu *internal.MutexByRoom // local cache of events for auth checks, etc - this may include events // which the roomserver is unaware of. haveEvents map[string]*gomatrixserverlib.HeaderedEvent @@ -494,6 +498,8 @@ func (t *txnReq) getServers(ctx context.Context, roomID string) []gomatrixserver } func (t *txnReq) processEvent(ctx context.Context, e *gomatrixserverlib.Event) error { + t.roomsMu.Lock(e.RoomID()) + defer t.roomsMu.Unlock(e.RoomID()) logger := util.GetLogger(ctx).WithField("event_id", e.EventID()).WithField("room_id", e.RoomID()) t.work = "" // reset from previous event diff --git a/federationapi/routing/send_test.go b/federationapi/routing/send_test.go index 8bdf54c4a..b14cbd35a 100644 --- a/federationapi/routing/send_test.go +++ b/federationapi/routing/send_test.go @@ -9,6 +9,7 @@ import ( "time" eduAPI "github.com/matrix-org/dendrite/eduserver/api" + "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal/test" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" @@ -370,6 +371,7 @@ func mustCreateTransaction(rsAPI api.RoomserverInternalAPI, fedClient txnFederat federation: fedClient, haveEvents: make(map[string]*gomatrixserverlib.HeaderedEvent), newEvents: make(map[string]bool), + roomsMu: internal.NewMutexByRoom(), } t.PDUs = pdus t.Origin = testOrigin diff --git a/internal/mutex.go b/internal/mutex.go new file mode 100644 index 000000000..3d36cdac9 --- /dev/null +++ b/internal/mutex.go @@ -0,0 +1,38 @@ +package internal + +import "sync" + +type MutexByRoom struct { + mu *sync.Mutex // protects the map + roomToMu map[string]*sync.Mutex +} + +func NewMutexByRoom() *MutexByRoom { + return &MutexByRoom{ + mu: &sync.Mutex{}, + roomToMu: make(map[string]*sync.Mutex), + } +} + +func (m *MutexByRoom) Lock(roomID string) { + m.mu.Lock() + roomMu := m.roomToMu[roomID] + if roomMu == nil { + roomMu = &sync.Mutex{} + } + m.roomToMu[roomID] = roomMu + m.mu.Unlock() + // don't lock inside m.mu else we can deadlock + roomMu.Lock() +} + +func (m *MutexByRoom) Unlock(roomID string) { + m.mu.Lock() + roomMu := m.roomToMu[roomID] + if roomMu == nil { + panic("MutexByRoom: Unlock before Lock") + } + m.mu.Unlock() + + roomMu.Unlock() +} From d27607af78a53bda636f14f603b02b2952d6e1d8 Mon Sep 17 00:00:00 2001 From: Bruce MacDonald Date: Wed, 7 Apr 2021 05:26:20 -0700 Subject: [PATCH 06/14] Implement OpenID module (#599) (#1812) * Implement OpenID module (#599) - Unrelated: change Riot references to Element in client API routing Signed-off-by: Bruce MacDonald * OpenID module tweaks (#599) - specify expiry is ms rather than vague ts - add OpenID token lifetime to configuration - use Go naming conventions for the path params - store plaintext token rather than hash - remove openid table sqllite mutex * Add default OpenID token lifetime (#599) * Update dendrite-config.yaml Co-authored-by: Kegsay Co-authored-by: Kegsay --- clientapi/routing/openid.go | 70 +++++++++++++++ clientapi/routing/routing.go | 19 +++- cmd/create-account/main.go | 2 +- dendrite-config.yaml | 5 ++ federationapi/routing/openid.go | 65 ++++++++++++++ federationapi/routing/routing.go | 6 ++ setup/base.go | 2 +- setup/config/config_userapi.go | 7 ++ setup/mscs/msc2836/msc2836_test.go | 6 ++ setup/mscs/msc2946/msc2946_test.go | 6 ++ sytest-whitelist | 3 + userapi/api/api.go | 41 +++++++++ userapi/internal/api.go | 28 ++++++ userapi/inthttp/client.go | 18 ++++ userapi/inthttp/server.go | 26 ++++++ userapi/storage/accounts/interface.go | 2 + .../storage/accounts/postgres/openid_table.go | 84 ++++++++++++++++++ userapi/storage/accounts/postgres/storage.go | 49 ++++++++--- .../storage/accounts/sqlite3/openid_table.go | 86 +++++++++++++++++++ userapi/storage/accounts/sqlite3/storage.go | 49 ++++++++--- userapi/storage/accounts/storage.go | 6 +- userapi/storage/accounts/storage_wasm.go | 3 +- userapi/userapi_test.go | 2 +- 23 files changed, 553 insertions(+), 32 deletions(-) create mode 100644 clientapi/routing/openid.go create mode 100644 federationapi/routing/openid.go create mode 100644 userapi/storage/accounts/postgres/openid_table.go create mode 100644 userapi/storage/accounts/sqlite3/openid_table.go diff --git a/clientapi/routing/openid.go b/clientapi/routing/openid.go new file mode 100644 index 000000000..13656e288 --- /dev/null +++ b/clientapi/routing/openid.go @@ -0,0 +1,70 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routing + +import ( + "net/http" + + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/util" +) + +type openIDTokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + MatrixServerName string `json:"matrix_server_name"` + ExpiresIn int64 `json:"expires_in"` +} + +// CreateOpenIDToken creates a new OpenID Connect (OIDC) token that a Matrix user +// can supply to an OpenID Relying Party to verify their identity +func CreateOpenIDToken( + req *http.Request, + userAPI api.UserInternalAPI, + device *api.Device, + userID string, + cfg *config.ClientAPI, +) util.JSONResponse { + // does the incoming user ID match the user that the token was issued for? + if userID != device.UserID { + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden("Cannot request tokens for other users"), + } + } + + request := api.PerformOpenIDTokenCreationRequest{ + UserID: userID, // this is the user ID from the incoming path + } + response := api.PerformOpenIDTokenCreationResponse{} + + err := userAPI.PerformOpenIDTokenCreation(req.Context(), &request, &response) + if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("userAPI.CreateOpenIDToken failed") + return jsonerror.InternalServerError() + } + + return util.JSONResponse{ + Code: http.StatusOK, + JSON: openIDTokenResponse{ + AccessToken: response.Token.Token, + TokenType: "Bearer", + MatrixServerName: string(cfg.Matrix.ServerName), + ExpiresIn: response.Token.ExpiresAtMS / 1000, // convert ms to s + }, + } +} diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index a56359b4c..5d4f90a45 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -469,7 +469,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) - // Stub endpoints required by Riot + // Stub endpoints required by Element r0mux.Handle("/login", httputil.MakeExternalAPI("login", func(req *http.Request) util.JSONResponse { @@ -506,7 +506,7 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) - // Riot user settings + // Element user settings r0mux.Handle("/profile/{userID}", httputil.MakeExternalAPI("profile", func(req *http.Request) util.JSONResponse { @@ -592,7 +592,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) - // Riot logs get flooded unless this is handled + // Element logs get flooded unless this is handled r0mux.Handle("/presence/{userID}/status", httputil.MakeExternalAPI("presence", func(req *http.Request) util.JSONResponse { if r := rateLimits.rateLimit(req); r != nil { @@ -685,6 +685,19 @@ func Setup( }), ).Methods(http.MethodGet) + r0mux.Handle("/user/{userID}/openid/request_token", + httputil.MakeAuthAPI("openid_request_token", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + if r := rateLimits.rateLimit(req); r != nil { + return *r + } + vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) + if err != nil { + return util.ErrorResponse(err) + } + return CreateOpenIDToken(req, userAPI, device, vars["userID"], cfg) + }), + ).Methods(http.MethodPost, http.MethodOptions) + r0mux.Handle("/user_directory/search", httputil.MakeAuthAPI("userdirectory_search", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.rateLimit(req); r != nil { diff --git a/cmd/create-account/main.go b/cmd/create-account/main.go index 22732c518..060b82f97 100644 --- a/cmd/create-account/main.go +++ b/cmd/create-account/main.go @@ -58,7 +58,7 @@ func main() { accountDB, err := accounts.NewDatabase(&config.DatabaseOptions{ ConnectionString: cfg.UserAPI.AccountDatabase.ConnectionString, - }, cfg.Global.ServerName, bcrypt.DefaultCost) + }, cfg.Global.ServerName, bcrypt.DefaultCost, cfg.UserAPI.OpenIDTokenLifetimeMS) if err != nil { logrus.Fatalln("Failed to connect to the database:", err.Error()) } diff --git a/dendrite-config.yaml b/dendrite-config.yaml index 402987f98..1edb026f7 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -360,6 +360,11 @@ user_api: max_open_conns: 10 max_idle_conns: 2 conn_max_lifetime: -1 + # The length of time that a token issued for a relying party from + # /_matrix/client/r0/user/{userId}/openid/request_token endpoint + # is considered to be valid in milliseconds. + # The default lifetime is 3600000ms (60 minutes). + # openid_token_lifetime_ms: 3600000 # Configuration for Opentracing. # See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on diff --git a/federationapi/routing/openid.go b/federationapi/routing/openid.go new file mode 100644 index 000000000..829dbccad --- /dev/null +++ b/federationapi/routing/openid.go @@ -0,0 +1,65 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routing + +import ( + "net/http" + "time" + + "github.com/matrix-org/dendrite/clientapi/jsonerror" + userapi "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/util" +) + +type openIDUserInfoResponse struct { + Sub string `json:"sub"` +} + +// GetOpenIDUserInfo implements GET /_matrix/federation/v1/openid/userinfo +func GetOpenIDUserInfo( + httpReq *http.Request, + userAPI userapi.UserInternalAPI, +) util.JSONResponse { + token := httpReq.URL.Query().Get("access_token") + if len(token) == 0 { + return util.JSONResponse{ + Code: http.StatusUnauthorized, + JSON: jsonerror.MissingArgument("access_token is missing"), + } + } + + req := userapi.QueryOpenIDTokenRequest{ + Token: token, + } + + var openIDTokenAttrResponse userapi.QueryOpenIDTokenResponse + err := userAPI.QueryOpenIDToken(httpReq.Context(), &req, &openIDTokenAttrResponse) + if err != nil { + util.GetLogger(httpReq.Context()).WithError(err).Error("userAPI.QueryOpenIDToken failed") + } + + var res interface{} = openIDUserInfoResponse{Sub: openIDTokenAttrResponse.Sub} + code := http.StatusOK + nowMS := time.Now().UnixNano() / int64(time.Millisecond) + if openIDTokenAttrResponse.Sub == "" || nowMS > openIDTokenAttrResponse.ExpiresAtMS { + code = http.StatusUnauthorized + res = jsonerror.UnknownToken("Access Token unknown or expired") + } + + return util.JSONResponse{ + Code: code, + JSON: res, + } +} diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index ce018904f..07a28c3fc 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -462,4 +462,10 @@ func Setup( return QueryDeviceKeys(httpReq, request, keyAPI, cfg.Matrix.ServerName) }, )).Methods(http.MethodPost) + + v1fedmux.Handle("/openid/userinfo", + httputil.MakeExternalAPI("federation_openid_userinfo", func(req *http.Request) util.JSONResponse { + return GetOpenIDUserInfo(req, userAPI) + }), + ).Methods(http.MethodGet) } diff --git a/setup/base.go b/setup/base.go index b081ffaf8..6bdeb80f7 100644 --- a/setup/base.go +++ b/setup/base.go @@ -280,7 +280,7 @@ func (b *BaseDendrite) KeyServerHTTPClient() keyserverAPI.KeyInternalAPI { // CreateAccountsDB creates a new instance of the accounts database. Should only // be called once per component. func (b *BaseDendrite) CreateAccountsDB() accounts.Database { - db, err := accounts.NewDatabase(&b.Cfg.UserAPI.AccountDatabase, b.Cfg.Global.ServerName, b.Cfg.UserAPI.BCryptCost) + db, err := accounts.NewDatabase(&b.Cfg.UserAPI.AccountDatabase, b.Cfg.Global.ServerName, b.Cfg.UserAPI.BCryptCost, b.Cfg.UserAPI.OpenIDTokenLifetimeMS) if err != nil { logrus.WithError(err).Panicf("failed to connect to accounts db") } diff --git a/setup/config/config_userapi.go b/setup/config/config_userapi.go index e69123842..2bf1be3dd 100644 --- a/setup/config/config_userapi.go +++ b/setup/config/config_userapi.go @@ -10,6 +10,9 @@ type UserAPI struct { // The cost when hashing passwords. BCryptCost int `yaml:"bcrypt_cost"` + // The length of time an OpenID token is condidered valid in milliseconds + OpenIDTokenLifetimeMS int64 `yaml:"openid_token_lifetime_ms"` + // The Account database stores the login details and account information // for local users. It is accessed by the UserAPI. AccountDatabase DatabaseOptions `yaml:"account_database"` @@ -18,6 +21,8 @@ type UserAPI struct { DeviceDatabase DatabaseOptions `yaml:"device_database"` } +const DefaultOpenIDTokenLifetimeMS = 3600000 // 60 minutes + func (c *UserAPI) Defaults() { c.InternalAPI.Listen = "http://localhost:7781" c.InternalAPI.Connect = "http://localhost:7781" @@ -26,6 +31,7 @@ func (c *UserAPI) Defaults() { c.AccountDatabase.ConnectionString = "file:userapi_accounts.db" c.DeviceDatabase.ConnectionString = "file:userapi_devices.db" c.BCryptCost = bcrypt.DefaultCost + c.OpenIDTokenLifetimeMS = DefaultOpenIDTokenLifetimeMS } func (c *UserAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { @@ -33,4 +39,5 @@ func (c *UserAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { checkURL(configErrs, "user_api.internal_api.connect", string(c.InternalAPI.Connect)) checkNotEmpty(configErrs, "user_api.account_database.connection_string", string(c.AccountDatabase.ConnectionString)) checkNotEmpty(configErrs, "user_api.device_database.connection_string", string(c.DeviceDatabase.ConnectionString)) + checkPositive(configErrs, "user_api.openid_token_lifetime_ms", c.OpenIDTokenLifetimeMS) } diff --git a/setup/mscs/msc2836/msc2836_test.go b/setup/mscs/msc2836/msc2836_test.go index 4eb5708c1..79aaebc0b 100644 --- a/setup/mscs/msc2836/msc2836_test.go +++ b/setup/mscs/msc2836/msc2836_test.go @@ -524,6 +524,9 @@ func (u *testUserAPI) PerformLastSeenUpdate(ctx context.Context, req *userapi.Pe func (u *testUserAPI) PerformAccountDeactivation(ctx context.Context, req *userapi.PerformAccountDeactivationRequest, res *userapi.PerformAccountDeactivationResponse) error { return nil } +func (u *testUserAPI) PerformOpenIDTokenCreation(ctx context.Context, req *userapi.PerformOpenIDTokenCreationRequest, res *userapi.PerformOpenIDTokenCreationResponse) error { + return nil +} func (u *testUserAPI) QueryProfile(ctx context.Context, req *userapi.QueryProfileRequest, res *userapi.QueryProfileResponse) error { return nil } @@ -548,6 +551,9 @@ func (u *testUserAPI) QueryDeviceInfos(ctx context.Context, req *userapi.QueryDe func (u *testUserAPI) QuerySearchProfiles(ctx context.Context, req *userapi.QuerySearchProfilesRequest, res *userapi.QuerySearchProfilesResponse) error { return nil } +func (u *testUserAPI) QueryOpenIDToken(ctx context.Context, req *userapi.QueryOpenIDTokenRequest, res *userapi.QueryOpenIDTokenResponse) error { + return nil +} type testRoomserverAPI struct { // use a trace API as it implements method stubs so we don't need to have them here. diff --git a/setup/mscs/msc2946/msc2946_test.go b/setup/mscs/msc2946/msc2946_test.go index 99085c0f4..96160c10d 100644 --- a/setup/mscs/msc2946/msc2946_test.go +++ b/setup/mscs/msc2946/msc2946_test.go @@ -367,6 +367,9 @@ func (u *testUserAPI) PerformLastSeenUpdate(ctx context.Context, req *userapi.Pe func (u *testUserAPI) PerformAccountDeactivation(ctx context.Context, req *userapi.PerformAccountDeactivationRequest, res *userapi.PerformAccountDeactivationResponse) error { return nil } +func (u *testUserAPI) PerformOpenIDTokenCreation(ctx context.Context, req *userapi.PerformOpenIDTokenCreationRequest, res *userapi.PerformOpenIDTokenCreationResponse) error { + return nil +} func (u *testUserAPI) QueryProfile(ctx context.Context, req *userapi.QueryProfileRequest, res *userapi.QueryProfileResponse) error { return nil } @@ -391,6 +394,9 @@ func (u *testUserAPI) QueryDeviceInfos(ctx context.Context, req *userapi.QueryDe func (u *testUserAPI) QuerySearchProfiles(ctx context.Context, req *userapi.QuerySearchProfilesRequest, res *userapi.QuerySearchProfilesResponse) error { return nil } +func (u *testUserAPI) QueryOpenIDToken(ctx context.Context, req *userapi.QueryOpenIDTokenRequest, res *userapi.QueryOpenIDTokenResponse) error { + return nil +} type testRoomserverAPI struct { // use a trace API as it implements method stubs so we don't need to have them here. diff --git a/sytest-whitelist b/sytest-whitelist index ed02fdecb..8c4585716 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -517,3 +517,6 @@ AS can set avatar for ghosted users AS can set displayname for ghosted users Ghost user must register before joining room Inviting an AS-hosted user asks the AS server +Can generate a openid access_token that can be exchanged for information about a user +Invalid openid access tokens are rejected +Requests to userinfo without access tokens are rejected diff --git a/userapi/api/api.go b/userapi/api/api.go index 45e4e834e..407350123 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -32,12 +32,14 @@ type UserInternalAPI interface { PerformLastSeenUpdate(ctx context.Context, req *PerformLastSeenUpdateRequest, res *PerformLastSeenUpdateResponse) error PerformDeviceUpdate(ctx context.Context, req *PerformDeviceUpdateRequest, res *PerformDeviceUpdateResponse) error PerformAccountDeactivation(ctx context.Context, req *PerformAccountDeactivationRequest, res *PerformAccountDeactivationResponse) error + PerformOpenIDTokenCreation(ctx context.Context, req *PerformOpenIDTokenCreationRequest, res *PerformOpenIDTokenCreationResponse) error QueryProfile(ctx context.Context, req *QueryProfileRequest, res *QueryProfileResponse) error QueryAccessToken(ctx context.Context, req *QueryAccessTokenRequest, res *QueryAccessTokenResponse) error QueryDevices(ctx context.Context, req *QueryDevicesRequest, res *QueryDevicesResponse) error QueryAccountData(ctx context.Context, req *QueryAccountDataRequest, res *QueryAccountDataResponse) error QueryDeviceInfos(ctx context.Context, req *QueryDeviceInfosRequest, res *QueryDeviceInfosResponse) error QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error + QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error } // InputAccountDataRequest is the request for InputAccountData @@ -226,6 +228,27 @@ type PerformAccountDeactivationResponse struct { AccountDeactivated bool } +// PerformOpenIDTokenCreationRequest is the request for PerformOpenIDTokenCreation +type PerformOpenIDTokenCreationRequest struct { + UserID string +} + +// PerformOpenIDTokenCreationResponse is the response for PerformOpenIDTokenCreation +type PerformOpenIDTokenCreationResponse struct { + Token OpenIDToken +} + +// QueryOpenIDTokenRequest is the request for QueryOpenIDToken +type QueryOpenIDTokenRequest struct { + Token string +} + +// QueryOpenIDTokenResponse is the response for QueryOpenIDToken +type QueryOpenIDTokenResponse struct { + Sub string // The Matrix User ID that generated the token + ExpiresAtMS int64 +} + // Device represents a client's device (mobile, web, etc) type Device struct { ID string @@ -256,6 +279,24 @@ type Account struct { // TODO: Associations (e.g. with application services) } +// OpenIDToken represents an OpenID token +type OpenIDToken struct { + Token string + UserID string + ExpiresAtMS int64 +} + +// OpenIDTokenInfo represents the attributes associated with an issued OpenID token +type OpenIDTokenAttributes struct { + UserID string + ExpiresAtMS int64 +} + +// UserInfo is for returning information about the user an OpenID token was issued for +type UserInfo struct { + Sub string // The Matrix user's ID who generated the token +} + // ErrorForbidden is an error indicating that the supplied access token is forbidden type ErrorForbidden struct { Message string diff --git a/userapi/internal/api.go b/userapi/internal/api.go index 0d01afa19..21933c1c4 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -414,3 +414,31 @@ func (a *UserInternalAPI) PerformAccountDeactivation(ctx context.Context, req *a res.AccountDeactivated = err == nil return err } + +// PerformOpenIDTokenCreation creates a new token that a relying party uses to authenticate a user +func (a *UserInternalAPI) PerformOpenIDTokenCreation(ctx context.Context, req *api.PerformOpenIDTokenCreationRequest, res *api.PerformOpenIDTokenCreationResponse) error { + token := util.RandomString(24) + + exp, err := a.AccountDB.CreateOpenIDToken(ctx, token, req.UserID) + + res.Token = api.OpenIDToken{ + Token: token, + UserID: req.UserID, + ExpiresAtMS: exp, + } + + return err +} + +// QueryOpenIDToken validates that the OpenID token was issued for the user, the replying party uses this for validation +func (a *UserInternalAPI) QueryOpenIDToken(ctx context.Context, req *api.QueryOpenIDTokenRequest, res *api.QueryOpenIDTokenResponse) error { + openIDTokenAttrs, err := a.AccountDB.GetOpenIDTokenAttributes(ctx, req.Token) + if err != nil { + return err + } + + res.Sub = openIDTokenAttrs.UserID + res.ExpiresAtMS = openIDTokenAttrs.ExpiresAtMS + + return nil +} diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go index 680e4cb52..1cb5ef0a8 100644 --- a/userapi/inthttp/client.go +++ b/userapi/inthttp/client.go @@ -35,6 +35,7 @@ const ( PerformLastSeenUpdatePath = "/userapi/performLastSeenUpdate" PerformDeviceUpdatePath = "/userapi/performDeviceUpdate" PerformAccountDeactivationPath = "/userapi/performAccountDeactivation" + PerformOpenIDTokenCreationPath = "/userapi/performOpenIDTokenCreation" QueryProfilePath = "/userapi/queryProfile" QueryAccessTokenPath = "/userapi/queryAccessToken" @@ -42,6 +43,7 @@ const ( QueryAccountDataPath = "/userapi/queryAccountData" QueryDeviceInfosPath = "/userapi/queryDeviceInfos" QuerySearchProfilesPath = "/userapi/querySearchProfiles" + QueryOpenIDTokenPath = "/userapi/queryOpenIDToken" ) // NewUserAPIClient creates a UserInternalAPI implemented by talking to a HTTP POST API. @@ -148,6 +150,14 @@ func (h *httpUserInternalAPI) PerformAccountDeactivation(ctx context.Context, re return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } +func (h *httpUserInternalAPI) PerformOpenIDTokenCreation(ctx context.Context, request *api.PerformOpenIDTokenCreationRequest, response *api.PerformOpenIDTokenCreationResponse) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformOpenIDTokenCreation") + defer span.Finish() + + apiURL := h.apiURL + PerformOpenIDTokenCreationPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} + func (h *httpUserInternalAPI) QueryProfile( ctx context.Context, request *api.QueryProfileRequest, @@ -207,3 +217,11 @@ func (h *httpUserInternalAPI) QuerySearchProfiles(ctx context.Context, req *api. apiURL := h.apiURL + QuerySearchProfilesPath return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } + +func (h *httpUserInternalAPI) QueryOpenIDToken(ctx context.Context, req *api.QueryOpenIDTokenRequest, res *api.QueryOpenIDTokenResponse) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryOpenIDToken") + defer span.Finish() + + apiURL := h.apiURL + QueryOpenIDTokenPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go index e495e3536..1c1cfdcd1 100644 --- a/userapi/inthttp/server.go +++ b/userapi/inthttp/server.go @@ -117,6 +117,19 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(PerformOpenIDTokenCreationPath, + httputil.MakeInternalAPI("performOpenIDTokenCreation", func(req *http.Request) util.JSONResponse { + request := api.PerformOpenIDTokenCreationRequest{} + response := api.PerformOpenIDTokenCreationResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + if err := s.PerformOpenIDTokenCreation(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) internalAPIMux.Handle(QueryProfilePath, httputil.MakeInternalAPI("queryProfile", func(req *http.Request) util.JSONResponse { request := api.QueryProfileRequest{} @@ -195,6 +208,19 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(QueryOpenIDTokenPath, + httputil.MakeInternalAPI("queryOpenIDToken", func(req *http.Request) util.JSONResponse { + request := api.QueryOpenIDTokenRequest{} + response := api.QueryOpenIDTokenResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + if err := s.QueryOpenIDToken(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) internalAPIMux.Handle(InputAccountDataPath, httputil.MakeInternalAPI("inputAccountDataPath", func(req *http.Request) util.JSONResponse { request := api.InputAccountDataRequest{} diff --git a/userapi/storage/accounts/interface.go b/userapi/storage/accounts/interface.go index c86b2c391..5aa61b909 100644 --- a/userapi/storage/accounts/interface.go +++ b/userapi/storage/accounts/interface.go @@ -52,6 +52,8 @@ type Database interface { GetAccountByLocalpart(ctx context.Context, localpart string) (*api.Account, error) SearchProfiles(ctx context.Context, searchString string, limit int) ([]authtypes.Profile, error) DeactivateAccount(ctx context.Context, localpart string) (err error) + CreateOpenIDToken(ctx context.Context, token, localpart string) (exp int64, err error) + GetOpenIDTokenAttributes(ctx context.Context, token string) (*api.OpenIDTokenAttributes, error) } // Err3PIDInUse is the error returned when trying to save an association involving diff --git a/userapi/storage/accounts/postgres/openid_table.go b/userapi/storage/accounts/postgres/openid_table.go new file mode 100644 index 000000000..86c197059 --- /dev/null +++ b/userapi/storage/accounts/postgres/openid_table.go @@ -0,0 +1,84 @@ +package postgres + +import ( + "context" + "database/sql" + + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/gomatrixserverlib" + log "github.com/sirupsen/logrus" +) + +const openIDTokenSchema = ` +-- Stores data about openid tokens issued for accounts. +CREATE TABLE IF NOT EXISTS open_id_tokens ( + -- The value of the token issued to a user + token TEXT NOT NULL PRIMARY KEY, + -- The Matrix user ID for this account + localpart TEXT NOT NULL, + -- When the token expires, as a unix timestamp (ms resolution). + token_expires_at_ms BIGINT NOT NULL +); +` + +const insertTokenSQL = "" + + "INSERT INTO open_id_tokens(token, localpart, token_expires_at_ms) VALUES ($1, $2, $3)" + +const selectTokenSQL = "" + + "SELECT localpart, token_expires_at_ms FROM open_id_tokens WHERE token = $1" + +type tokenStatements struct { + insertTokenStmt *sql.Stmt + selectTokenStmt *sql.Stmt + serverName gomatrixserverlib.ServerName +} + +func (s *tokenStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) { + _, err = db.Exec(openIDTokenSchema) + if err != nil { + return + } + if s.insertTokenStmt, err = db.Prepare(insertTokenSQL); err != nil { + return + } + if s.selectTokenStmt, err = db.Prepare(selectTokenSQL); err != nil { + return + } + s.serverName = server + return +} + +// insertToken inserts a new OpenID Connect token to the DB. +// Returns new token, otherwise returns error if the token already exists. +func (s *tokenStatements) insertToken( + ctx context.Context, + txn *sql.Tx, + token, localpart string, + expiresAtMS int64, +) (err error) { + stmt := sqlutil.TxStmt(txn, s.insertTokenStmt) + _, err = stmt.ExecContext(ctx, token, localpart, expiresAtMS) + return +} + +// selectOpenIDTokenAtrributes gets the attributes associated with an OpenID token from the DB +// Returns the existing token's attributes, or err if no token is found +func (s *tokenStatements) selectOpenIDTokenAtrributes( + ctx context.Context, + token string, +) (*api.OpenIDTokenAttributes, error) { + var openIDTokenAttrs api.OpenIDTokenAttributes + err := s.selectTokenStmt.QueryRowContext(ctx, token).Scan( + &openIDTokenAttrs.UserID, + &openIDTokenAttrs.ExpiresAtMS, + ) + if err != nil { + if err != sql.ErrNoRows { + log.WithError(err).Error("Unable to retrieve token from the db") + } + return nil, err + } + + return &openIDTokenAttrs, nil +} diff --git a/userapi/storage/accounts/postgres/storage.go b/userapi/storage/accounts/postgres/storage.go index 3933fe5bd..c5e74ed15 100644 --- a/userapi/storage/accounts/postgres/storage.go +++ b/userapi/storage/accounts/postgres/storage.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "strconv" + "time" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/internal/sqlutil" @@ -39,25 +40,28 @@ type Database struct { db *sql.DB writer sqlutil.Writer sqlutil.PartitionOffsetStatements - accounts accountsStatements - profiles profilesStatements - accountDatas accountDataStatements - threepids threepidStatements - serverName gomatrixserverlib.ServerName - bcryptCost int + accounts accountsStatements + profiles profilesStatements + accountDatas accountDataStatements + threepids threepidStatements + openIDTokens tokenStatements + serverName gomatrixserverlib.ServerName + bcryptCost int + openIDTokenLifetimeMS int64 } // NewDatabase creates a new accounts and profiles database -func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (*Database, error) { +func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int, openIDTokenLifetimeMS int64) (*Database, error) { db, err := sqlutil.Open(dbProperties) if err != nil { return nil, err } d := &Database{ - serverName: serverName, - db: db, - writer: sqlutil.NewDummyWriter(), - bcryptCost: bcryptCost, + serverName: serverName, + db: db, + writer: sqlutil.NewDummyWriter(), + bcryptCost: bcryptCost, + openIDTokenLifetimeMS: openIDTokenLifetimeMS, } // Create tables before executing migrations so we don't fail if the table is missing, @@ -86,6 +90,9 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver if err = d.threepids.prepare(db); err != nil { return nil, err } + if err = d.openIDTokens.prepare(db, serverName); err != nil { + return nil, err + } return d, nil } @@ -341,3 +348,23 @@ func (d *Database) SearchProfiles(ctx context.Context, searchString string, limi func (d *Database) DeactivateAccount(ctx context.Context, localpart string) (err error) { return d.accounts.deactivateAccount(ctx, localpart) } + +// CreateOpenIDToken persists a new token that was issued through OpenID Connect +func (d *Database) CreateOpenIDToken( + ctx context.Context, + token, localpart string, +) (int64, error) { + expiresAtMS := time.Now().UnixNano()/int64(time.Millisecond) + d.openIDTokenLifetimeMS + err := sqlutil.WithTransaction(d.db, func(txn *sql.Tx) error { + return d.openIDTokens.insertToken(ctx, txn, token, localpart, expiresAtMS) + }) + return expiresAtMS, err +} + +// GetOpenIDTokenAttributes gets the attributes of issued an OIDC auth token +func (d *Database) GetOpenIDTokenAttributes( + ctx context.Context, + token string, +) (*api.OpenIDTokenAttributes, error) { + return d.openIDTokens.selectOpenIDTokenAtrributes(ctx, token) +} diff --git a/userapi/storage/accounts/sqlite3/openid_table.go b/userapi/storage/accounts/sqlite3/openid_table.go new file mode 100644 index 000000000..80b9dd4cb --- /dev/null +++ b/userapi/storage/accounts/sqlite3/openid_table.go @@ -0,0 +1,86 @@ +package sqlite3 + +import ( + "context" + "database/sql" + + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/gomatrixserverlib" + log "github.com/sirupsen/logrus" +) + +const openIDTokenSchema = ` +-- Stores data about accounts. +CREATE TABLE IF NOT EXISTS open_id_tokens ( + -- The value of the token issued to a user + token TEXT NOT NULL PRIMARY KEY, + -- The Matrix user ID for this account + localpart TEXT NOT NULL, + -- When the token expires, as a unix timestamp (ms resolution). + token_expires_at_ms BIGINT NOT NULL +); +` + +const insertTokenSQL = "" + + "INSERT INTO open_id_tokens(token, localpart, token_expires_at_ms) VALUES ($1, $2, $3)" + +const selectTokenSQL = "" + + "SELECT localpart, token_expires_at_ms FROM open_id_tokens WHERE token = $1" + +type tokenStatements struct { + db *sql.DB + insertTokenStmt *sql.Stmt + selectTokenStmt *sql.Stmt + serverName gomatrixserverlib.ServerName +} + +func (s *tokenStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) { + s.db = db + _, err = db.Exec(openIDTokenSchema) + if err != nil { + return err + } + if s.insertTokenStmt, err = db.Prepare(insertTokenSQL); err != nil { + return + } + if s.selectTokenStmt, err = db.Prepare(selectTokenSQL); err != nil { + return + } + s.serverName = server + return +} + +// insertToken inserts a new OpenID Connect token to the DB. +// Returns new token, otherwise returns error if the token already exists. +func (s *tokenStatements) insertToken( + ctx context.Context, + txn *sql.Tx, + token, localpart string, + expiresAtMS int64, +) (err error) { + stmt := sqlutil.TxStmt(txn, s.insertTokenStmt) + _, err = stmt.ExecContext(ctx, token, localpart, expiresAtMS) + return +} + +// selectOpenIDTokenAtrributes gets the attributes associated with an OpenID token from the DB +// Returns the existing token's attributes, or err if no token is found +func (s *tokenStatements) selectOpenIDTokenAtrributes( + ctx context.Context, + token string, +) (*api.OpenIDTokenAttributes, error) { + var openIDTokenAttrs api.OpenIDTokenAttributes + err := s.selectTokenStmt.QueryRowContext(ctx, token).Scan( + &openIDTokenAttrs.UserID, + &openIDTokenAttrs.ExpiresAtMS, + ) + if err != nil { + if err != sql.ErrNoRows { + log.WithError(err).Error("Unable to retrieve token from the db") + } + return nil, err + } + + return &openIDTokenAttrs, nil +} diff --git a/userapi/storage/accounts/sqlite3/storage.go b/userapi/storage/accounts/sqlite3/storage.go index 07cc68b35..c0f7118cb 100644 --- a/userapi/storage/accounts/sqlite3/storage.go +++ b/userapi/storage/accounts/sqlite3/storage.go @@ -21,6 +21,7 @@ import ( "errors" "strconv" "sync" + "time" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/internal/sqlutil" @@ -37,12 +38,14 @@ type Database struct { writer sqlutil.Writer sqlutil.PartitionOffsetStatements - accounts accountsStatements - profiles profilesStatements - accountDatas accountDataStatements - threepids threepidStatements - serverName gomatrixserverlib.ServerName - bcryptCost int + accounts accountsStatements + profiles profilesStatements + accountDatas accountDataStatements + threepids threepidStatements + openIDTokens tokenStatements + serverName gomatrixserverlib.ServerName + bcryptCost int + openIDTokenLifetimeMS int64 accountsMu sync.Mutex profilesMu sync.Mutex @@ -51,16 +54,17 @@ type Database struct { } // NewDatabase creates a new accounts and profiles database -func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (*Database, error) { +func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int, openIDTokenLifetimeMS int64) (*Database, error) { db, err := sqlutil.Open(dbProperties) if err != nil { return nil, err } d := &Database{ - serverName: serverName, - db: db, - writer: sqlutil.NewExclusiveWriter(), - bcryptCost: bcryptCost, + serverName: serverName, + db: db, + writer: sqlutil.NewExclusiveWriter(), + bcryptCost: bcryptCost, + openIDTokenLifetimeMS: openIDTokenLifetimeMS, } // Create tables before executing migrations so we don't fail if the table is missing, @@ -90,6 +94,9 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver if err = d.threepids.prepare(db); err != nil { return nil, err } + if err = d.openIDTokens.prepare(db, serverName); err != nil { + return nil, err + } return d, nil } @@ -379,3 +386,23 @@ func (d *Database) SearchProfiles(ctx context.Context, searchString string, limi func (d *Database) DeactivateAccount(ctx context.Context, localpart string) (err error) { return d.accounts.deactivateAccount(ctx, localpart) } + +// CreateOpenIDToken persists a new token that was issued for OpenID Connect +func (d *Database) CreateOpenIDToken( + ctx context.Context, + token, localpart string, +) (int64, error) { + expiresAtMS := time.Now().UnixNano()/int64(time.Millisecond) + d.openIDTokenLifetimeMS + err := d.writer.Do(d.db, nil, func(txn *sql.Tx) error { + return d.openIDTokens.insertToken(ctx, txn, token, localpart, expiresAtMS) + }) + return expiresAtMS, err +} + +// GetOpenIDTokenAttributes gets the attributes of issued an OIDC auth token +func (d *Database) GetOpenIDTokenAttributes( + ctx context.Context, + token string, +) (*api.OpenIDTokenAttributes, error) { + return d.openIDTokens.selectOpenIDTokenAtrributes(ctx, token) +} diff --git a/userapi/storage/accounts/storage.go b/userapi/storage/accounts/storage.go index 28c437daa..3489c9d07 100644 --- a/userapi/storage/accounts/storage.go +++ b/userapi/storage/accounts/storage.go @@ -27,12 +27,12 @@ import ( // NewDatabase opens a new Postgres or Sqlite database (based on dataSourceName scheme) // and sets postgres connection parameters -func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (Database, error) { +func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int, openIDTokenLifetimeMS int64) (Database, error) { switch { case dbProperties.ConnectionString.IsSQLite(): - return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost) + return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost, openIDTokenLifetimeMS) case dbProperties.ConnectionString.IsPostgres(): - return postgres.NewDatabase(dbProperties, serverName, bcryptCost) + return postgres.NewDatabase(dbProperties, serverName, bcryptCost, openIDTokenLifetimeMS) default: return nil, fmt.Errorf("unexpected database type") } diff --git a/userapi/storage/accounts/storage_wasm.go b/userapi/storage/accounts/storage_wasm.go index 8038326fe..11a88a20a 100644 --- a/userapi/storage/accounts/storage_wasm.go +++ b/userapi/storage/accounts/storage_wasm.go @@ -26,10 +26,11 @@ func NewDatabase( dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int, + openIDTokenLifetimeMS int64, ) (Database, error) { switch { case dbProperties.ConnectionString.IsSQLite(): - return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost) + return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost, openIDTokenLifetimeMS) case dbProperties.ConnectionString.IsPostgres(): return nil, fmt.Errorf("can't use Postgres implementation") default: diff --git a/userapi/userapi_test.go b/userapi/userapi_test.go index 9a45a2dc8..0141258e6 100644 --- a/userapi/userapi_test.go +++ b/userapi/userapi_test.go @@ -26,7 +26,7 @@ const ( func MustMakeInternalAPI(t *testing.T) (api.UserInternalAPI, accounts.Database) { accountDB, err := accounts.NewDatabase(&config.DatabaseOptions{ ConnectionString: "file::memory:", - }, serverName, bcrypt.MinCost) + }, serverName, bcrypt.MinCost, config.DefaultOpenIDTokenLifetimeMS) if err != nil { t.Fatalf("failed to create account DB: %s", err) } From 5ade348d142012367e6cf4b8c2c65d6fbf357af6 Mon Sep 17 00:00:00 2001 From: Tim McCormack Date: Thu, 8 Apr 2021 11:08:38 +0000 Subject: [PATCH 07/14] Document need for max-body-size change in reverse proxy as well (#1816) Just changing the Media API's `max_file_size_bytes` isn't enough if Dendrite is running behind a proxy; document the need for a proxy config change in the place the admin is most likely to notice it. Signed-off-by: Tim McCormack --- dendrite-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dendrite-config.yaml b/dendrite-config.yaml index 1edb026f7..0ea584aa9 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -248,7 +248,8 @@ media_api: base_path: ./media_store # The maximum allowed file size (in bytes) for media uploads to this homeserver - # (0 = unlimited). + # (0 = unlimited). If using a reverse proxy, ensure it allows requests at + # least this large (e.g. client_max_body_size in nginx.) max_file_size_bytes: 10485760 # Whether to dynamically generate thumbnails if needed. From b769d5a25ee1dd298899ab9fec0c31e834f9b99e Mon Sep 17 00:00:00 2001 From: Kegsay Date: Thu, 8 Apr 2021 13:50:39 +0100 Subject: [PATCH 08/14] Optimise memory usage when calling /g_m_e (#1819) * Optimise memory usage when calling /g_m_e * cache more events * refactor handling of device list update pokes * Sigh --- federationapi/routing/send.go | 44 +++++++++------ keyserver/internal/device_list_update.go | 68 +++++++++++------------- 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index b48d6c0b8..fee6d565f 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -620,7 +620,9 @@ func checkAllowedByState(e *gomatrixserverlib.Event, stateEvents []*gomatrixserv return gomatrixserverlib.Allowed(e, &authUsingState) } -func (t *txnReq) processEventWithMissingState(ctx context.Context, e *gomatrixserverlib.Event, roomVersion gomatrixserverlib.RoomVersion) error { +func (t *txnReq) processEventWithMissingState( + ctx context.Context, e *gomatrixserverlib.Event, roomVersion gomatrixserverlib.RoomVersion, +) error { // Do this with a fresh context, so that we keep working even if the // original request times out. With any luck, by the time the remote // side retries, we'll have fetched the missing state. @@ -784,7 +786,7 @@ func (t *txnReq) lookupStateAfterEvent(ctx context.Context, roomVersion gomatrix default: return nil, false, fmt.Errorf("t.lookupEvent: %w", err) } - t.haveEvents[h.EventID()] = h + t.cacheAndReturn(h) if h.StateKey() != nil { addedToState := false for i := range respState.StateEvents { @@ -803,6 +805,14 @@ func (t *txnReq) lookupStateAfterEvent(ctx context.Context, roomVersion gomatrix return respState, false, nil } +func (t *txnReq) cacheAndReturn(ev *gomatrixserverlib.HeaderedEvent) *gomatrixserverlib.HeaderedEvent { + if cached, exists := t.haveEvents[ev.EventID()]; exists { + return cached + } + t.haveEvents[ev.EventID()] = ev + return ev +} + func (t *txnReq) lookupStateAfterEventLocally(ctx context.Context, roomID, eventID string) *gomatrixserverlib.RespState { var res api.QueryStateAfterEventsResponse err := t.rsAPI.QueryStateAfterEvents(ctx, &api.QueryStateAfterEventsRequest{ @@ -810,15 +820,21 @@ func (t *txnReq) lookupStateAfterEventLocally(ctx context.Context, roomID, event PrevEventIDs: []string{eventID}, }, &res) if err != nil || !res.PrevEventsExist { - util.GetLogger(ctx).WithError(err).Warnf("failed to query state after %s locally", eventID) + util.GetLogger(ctx).WithField("room_id", roomID).WithError(err).Warnf("failed to query state after %s locally, prev exists=%v", eventID, res.PrevEventsExist) return nil } + stateEvents := make([]*gomatrixserverlib.HeaderedEvent, len(res.StateEvents)) for i, ev := range res.StateEvents { - t.haveEvents[ev.EventID()] = res.StateEvents[i] + // set the event from the haveEvents cache - this means we will share pointers with other prev_event branches for this + // processEvent request, which is better for memory. + stateEvents[i] = t.cacheAndReturn(ev) } + // we should never access res.StateEvents again so we delete it here to make GC faster + res.StateEvents = nil + var authEvents []*gomatrixserverlib.Event missingAuthEvents := map[string]bool{} - for _, ev := range res.StateEvents { + for _, ev := range stateEvents { for _, ae := range ev.AuthEventIDs() { if aev, ok := t.haveEvents[ae]; ok { authEvents = append(authEvents, aev.Unwrap()) @@ -843,14 +859,13 @@ func (t *txnReq) lookupStateAfterEventLocally(ctx context.Context, roomID, event return nil } for i := range queryRes.Events { - evID := queryRes.Events[i].EventID() - t.haveEvents[evID] = queryRes.Events[i] - authEvents = append(authEvents, queryRes.Events[i].Unwrap()) + authEvents = append(authEvents, t.cacheAndReturn(queryRes.Events[i]).Unwrap()) } + queryRes.Events = nil } return &gomatrixserverlib.RespState{ - StateEvents: gomatrixserverlib.UnwrapEventHeaders(res.StateEvents), + StateEvents: gomatrixserverlib.UnwrapEventHeaders(stateEvents), AuthEvents: authEvents, } } @@ -860,8 +875,6 @@ func (t *txnReq) lookupStateAfterEventLocally(ctx context.Context, roomID, event func (t *txnReq) lookupStateBeforeEvent(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomID, eventID string) ( *gomatrixserverlib.RespState, error) { - util.GetLogger(ctx).Infof("lookupStateBeforeEvent %s", eventID) - // Attempt to fetch the missing state using /state_ids and /events return t.lookupMissingStateViaStateIDs(ctx, roomID, eventID, roomVersion) } @@ -992,7 +1005,6 @@ Event: } } - // we processed everything! return newEvents, nil } @@ -1011,7 +1023,7 @@ func (t *txnReq) lookupMissingStateViaState(ctx context.Context, roomID, eventID func (t *txnReq) lookupMissingStateViaStateIDs(ctx context.Context, roomID, eventID string, roomVersion gomatrixserverlib.RoomVersion) ( *gomatrixserverlib.RespState, error) { - util.GetLogger(ctx).Infof("lookupMissingStateViaStateIDs %s", eventID) + util.GetLogger(ctx).WithField("room_id", roomID).Infof("lookupMissingStateViaStateIDs %s", eventID) // fetch the state event IDs at the time of the event stateIDs, err := t.federation.LookupStateIDs(ctx, t.Origin, roomID, eventID) if err != nil { @@ -1040,14 +1052,16 @@ func (t *txnReq) lookupMissingStateViaStateIDs(ctx context.Context, roomID, even } for i := range queryRes.Events { evID := queryRes.Events[i].EventID() - t.haveEvents[evID] = queryRes.Events[i] + t.cacheAndReturn(queryRes.Events[i]) if missing[evID] { delete(missing, evID) } } + queryRes.Events = nil // allow it to be GCed concurrentRequests := 8 missingCount := len(missing) + util.GetLogger(ctx).WithField("room_id", roomID).WithField("event_id", eventID).Infof("lookupMissingStateViaStateIDs missing %d/%d events", missingCount, len(wantIDs)) // If over 50% of the auth/state events from /state_ids are missing // then we'll just call /state instead, otherwise we'll just end up @@ -1112,7 +1126,7 @@ func (t *txnReq) lookupMissingStateViaStateIDs(ctx context.Context, roomID, even return } haveEventsMutex.Lock() - t.haveEvents[h.EventID()] = h + t.cacheAndReturn(h) haveEventsMutex.Unlock() } diff --git a/keyserver/internal/device_list_update.go b/keyserver/internal/device_list_update.go index bd563ef35..47bfb72c3 100644 --- a/keyserver/internal/device_list_update.go +++ b/keyserver/internal/device_list_update.go @@ -72,10 +72,8 @@ func init() { // we guarantee we will get around to it. Also, more users on a given server does not increase the number of requests // (as /keys/query allows multiple users to be specified) so being stuck behind matrix.org won't materially be any worse // than being stuck behind foo.bar -// In the event that the query fails, the worker spins up a short-lived goroutine whose sole purpose is to inject the server -// name back into the channel after a certain amount of time. If in the interim the device lists have been updated, then -// the database query will return no stale lists. Reinjection into the channel continues until success or the server terminates, -// when it will be reloaded on startup. +// In the event that the query fails, a lock is acquired and the server name along with the time to wait before retrying is +// set in a map. A restarter goroutine periodically probes this map and injects servers which are ready to be retried. type DeviceListUpdater struct { // A map from user_id to a mutex. Used when we are missing prev IDs so we don't make more than 1 // request to the remote server and race. @@ -297,42 +295,38 @@ func (u *DeviceListUpdater) clearChannel(userID string) { } func (u *DeviceListUpdater) worker(ch chan gomatrixserverlib.ServerName) { - // It's possible to get many of the same server name in the channel, so in order - // to prevent processing the same server over and over we keep track of when we - // last made a request to the server. If we get the server name during the cooloff - // period, we'll ignore the poke. - lastProcessed := make(map[gomatrixserverlib.ServerName]time.Time) - // this can't be too long else sytest will give up trying to do a test - cooloffPeriod := 500 * time.Millisecond - shouldProcess := func(srv gomatrixserverlib.ServerName) bool { - // we should process requests when now is after the last process time + cooloff - return time.Now().After(lastProcessed[srv].Add(cooloffPeriod)) - } - - // on failure, spin up a short-lived goroutine to inject the server name again. - scheduledRetries := make(map[gomatrixserverlib.ServerName]time.Time) - inject := func(srv gomatrixserverlib.ServerName, duration time.Duration) { - time.Sleep(duration) - ch <- srv - } - - for serverName := range ch { - if !shouldProcess(serverName) { - if time.Now().Before(scheduledRetries[serverName]) { - // do not inject into the channel as we know there will be a sleeping goroutine - // which will do it after the cooloff period expires - continue - } else { - scheduledRetries[serverName] = time.Now().Add(cooloffPeriod) - go inject(serverName, cooloffPeriod) - continue + retries := make(map[gomatrixserverlib.ServerName]time.Time) + retriesMu := &sync.Mutex{} + // restarter goroutine which will inject failed servers into ch when it is time + go func() { + for { + var serversToRetry []gomatrixserverlib.ServerName + time.Sleep(time.Second) + retriesMu.Lock() + now := time.Now() + for srv, retryAt := range retries { + if now.After(retryAt) { + serversToRetry = append(serversToRetry, srv) + } + } + for _, srv := range serversToRetry { + delete(retries, srv) + } + retriesMu.Unlock() + for _, srv := range serversToRetry { + ch <- srv } } - lastProcessed[serverName] = time.Now() + }() + for serverName := range ch { waitTime, shouldRetry := u.processServer(serverName) if shouldRetry { - scheduledRetries[serverName] = time.Now().Add(waitTime) - go inject(serverName, waitTime) + retriesMu.Lock() + _, exists := retries[serverName] + if !exists { + retries[serverName] = time.Now().Add(waitTime) + } + retriesMu.Unlock() } } } @@ -380,7 +374,7 @@ func (u *DeviceListUpdater) processServer(serverName gomatrixserverlib.ServerNam } } if failCount > 0 { - logger.WithField("total", len(userIDs)).WithField("failed", failCount).Error("failed to query device keys for some users") + logger.WithField("total", len(userIDs)).WithField("failed", failCount).WithField("wait", waitTime).Error("failed to query device keys for some users") } for _, userID := range userIDs { // always clear the channel to unblock Update calls regardless of success/failure From e08942fb00433420af72dc401f44586623d78c41 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Fri, 9 Apr 2021 10:21:35 +0100 Subject: [PATCH 09/14] Remove legacy register endpoint (#1822) * Remove legacy register endpoint We only support `/r0` CS API paths, not `/v1`. * Finish removing --- clientapi/routing/register.go | 88 ----------------------------------- clientapi/routing/routing.go | 8 ---- 2 files changed, 96 deletions(-) diff --git a/clientapi/routing/register.go b/clientapi/routing/register.go index 8e5a6b9b1..872bdd736 100644 --- a/clientapi/routing/register.go +++ b/clientapi/routing/register.go @@ -161,15 +161,6 @@ type userInteractiveResponse struct { Session string `json:"session"` } -// legacyRegisterRequest represents the submitted registration request for v1 API. -type legacyRegisterRequest struct { - Password string `json:"password"` - Username string `json:"user"` - Admin bool `json:"admin"` - Type authtypes.LoginType `json:"type"` - Mac gomatrixserverlib.HexString `json:"mac"` -} - // newUserInteractiveResponse will return a struct to be sent back to the client // during registration. func newUserInteractiveResponse( @@ -757,85 +748,6 @@ func checkAndCompleteFlow( } } -// LegacyRegister process register requests from the legacy v1 API -func LegacyRegister( - req *http.Request, - userAPI userapi.UserInternalAPI, - cfg *config.ClientAPI, -) util.JSONResponse { - var r legacyRegisterRequest - resErr := parseAndValidateLegacyLogin(req, &r) - if resErr != nil { - return *resErr - } - - logger := util.GetLogger(req.Context()) - logger.WithFields(log.Fields{ - "username": r.Username, - "auth.type": r.Type, - }).Info("Processing registration request") - - if cfg.RegistrationDisabled && r.Type != authtypes.LoginTypeSharedSecret { - return util.MessageResponse(http.StatusForbidden, "Registration has been disabled") - } - - switch r.Type { - case authtypes.LoginTypeSharedSecret: - if cfg.RegistrationSharedSecret == "" { - return util.MessageResponse(http.StatusBadRequest, "Shared secret registration is disabled") - } - - valid, err := isValidMacLogin(cfg, r.Username, r.Password, r.Admin, r.Mac) - if err != nil { - util.GetLogger(req.Context()).WithError(err).Error("isValidMacLogin failed") - return jsonerror.InternalServerError() - } - - if !valid { - return util.MessageResponse(http.StatusForbidden, "HMAC incorrect") - } - - return completeRegistration(req.Context(), userAPI, r.Username, r.Password, "", req.RemoteAddr, req.UserAgent(), false, nil, nil) - case authtypes.LoginTypeDummy: - // there is nothing to do - return completeRegistration(req.Context(), userAPI, r.Username, r.Password, "", req.RemoteAddr, req.UserAgent(), false, nil, nil) - default: - return util.JSONResponse{ - Code: http.StatusNotImplemented, - JSON: jsonerror.Unknown("unknown/unimplemented auth type"), - } - } -} - -// parseAndValidateLegacyLogin parses the request into r and checks that the -// request is valid (e.g. valid user names, etc) -func parseAndValidateLegacyLogin(req *http.Request, r *legacyRegisterRequest) *util.JSONResponse { - resErr := httputil.UnmarshalJSONRequest(req, &r) - if resErr != nil { - return resErr - } - - // Squash username to all lowercase letters - r.Username = strings.ToLower(r.Username) - - if resErr = validateUsername(r.Username); resErr != nil { - return resErr - } - if resErr = validatePassword(r.Password); resErr != nil { - return resErr - } - - // All registration requests must specify what auth they are using to perform this request - if r.Type == "" { - return &util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.BadJSON("invalid type"), - } - } - - return nil -} - // completeRegistration runs some rudimentary checks against the submitted // input, then if successful creates an account and a newly associated device // We pass in each individual part of the request here instead of just passing a diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 5d4f90a45..9f980e0a9 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -89,7 +89,6 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux := publicAPIMux.PathPrefix("/r0").Subrouter() - v1mux := publicAPIMux.PathPrefix("/api/v1").Subrouter() unstableMux := publicAPIMux.PathPrefix("/unstable").Subrouter() r0mux.Handle("/createRoom", @@ -306,13 +305,6 @@ func Setup( return Register(req, userAPI, accountDB, cfg) })).Methods(http.MethodPost, http.MethodOptions) - v1mux.Handle("/register", httputil.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse { - if r := rateLimits.rateLimit(req); r != nil { - return *r - } - return LegacyRegister(req, userAPI, cfg) - })).Methods(http.MethodPost, http.MethodOptions) - r0mux.Handle("/register/available", httputil.MakeExternalAPI("registerAvailable", func(req *http.Request) util.JSONResponse { if r := rateLimits.rateLimit(req); r != nil { return *r From 080ae6a8291e2bafb569eb9f163f67f85275d678 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 13 Apr 2021 11:13:07 +0100 Subject: [PATCH 10/14] Move room mutex in federation API (#1830) * Move room mutex in federation API to surround resolveStatesAndCheck * Guard processEventWithMissingState instead * Revert "Guard processEventWithMissingState instead" This reverts commit 0ce88036aa79b0ce97e967afb70fe932a547d5f0. --- federationapi/routing/send.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index fee6d565f..708ba38ec 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -498,8 +498,6 @@ func (t *txnReq) getServers(ctx context.Context, roomID string) []gomatrixserver } func (t *txnReq) processEvent(ctx context.Context, e *gomatrixserverlib.Event) error { - t.roomsMu.Lock(e.RoomID()) - defer t.roomsMu.Unlock(e.RoomID()) logger := util.GetLogger(ctx).WithField("event_id", e.EventID()).WithField("room_id", e.RoomID()) t.work = "" // reset from previous event @@ -718,7 +716,9 @@ func (t *txnReq) processEventWithMissingState( respStates[i] = states[i].RespState } // There's more than one previous state - run them all through state res + t.roomsMu.Lock(e.RoomID()) resolvedState, err = t.resolveStatesAndCheck(gmectx, roomVersion, respStates, backwardsExtremity) + t.roomsMu.Unlock(e.RoomID()) if err != nil { util.GetLogger(ctx).WithError(err).Errorf("Failed to resolve state conflicts for event %s", backwardsExtremity.EventID()) return err From 653e30619cebdc6b0f234a5ab872abddfd311656 Mon Sep 17 00:00:00 2001 From: Fero Date: Wed, 14 Apr 2021 11:53:24 +0200 Subject: [PATCH 11/14] Remove the Content-Length requirement on upload request (#1831) * Remove the Content-Length requirement * Make sure that the file size does not exceed max content size * Address review comment - universally check if temp file size exceeds max file size --- mediaapi/routing/upload.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/mediaapi/routing/upload.go b/mediaapi/routing/upload.go index 2c5753745..a38b56e09 100644 --- a/mediaapi/routing/upload.go +++ b/mediaapi/routing/upload.go @@ -158,6 +158,12 @@ func (r *uploadRequest) doUpload( } } + // Check if temp file size exceeds max file size configuration + if bytesWritten > types.FileSizeBytes(*cfg.MaxFileSizeBytes) { + fileutils.RemoveDir(tmpDir, r.Logger) // delete temp file + return requestEntityTooLargeJSONResponse(*cfg.MaxFileSizeBytes) + } + // Look up the media by the file hash. If we already have the file but under a // different media ID then we won't upload the file again - instead we'll just // add a new metadata entry that refers to the same file. @@ -219,19 +225,17 @@ func (r *uploadRequest) doUpload( ) } +func requestEntityTooLargeJSONResponse(maxFileSizeBytes config.FileSizeBytes) *util.JSONResponse { + return &util.JSONResponse{ + Code: http.StatusRequestEntityTooLarge, + JSON: jsonerror.Unknown(fmt.Sprintf("HTTP Content-Length is greater than the maximum allowed upload size (%v).", maxFileSizeBytes)), + } +} + // Validate validates the uploadRequest fields func (r *uploadRequest) Validate(maxFileSizeBytes config.FileSizeBytes) *util.JSONResponse { - if r.MediaMetadata.FileSizeBytes < 1 { - return &util.JSONResponse{ - Code: http.StatusLengthRequired, - JSON: jsonerror.Unknown("HTTP Content-Length request header must be greater than zero."), - } - } if maxFileSizeBytes > 0 && r.MediaMetadata.FileSizeBytes > types.FileSizeBytes(maxFileSizeBytes) { - return &util.JSONResponse{ - Code: http.StatusRequestEntityTooLarge, - JSON: jsonerror.Unknown(fmt.Sprintf("HTTP Content-Length is greater than the maximum allowed upload size (%v).", maxFileSizeBytes)), - } + return requestEntityTooLargeJSONResponse(maxFileSizeBytes) } // TODO: Check if the Content-Type is a valid type? if r.MediaMetadata.ContentType == "" { From 656d11ec9005b5cc47e495229e924df5085065e0 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Wed, 14 Apr 2021 11:11:54 +0100 Subject: [PATCH 12/14] fedsender: tolerate dupe membership events (#1824) * fedsender: tolerate dupe membership events Previously if the fedsender got a duplicate membership event it would cause the entire process to crash. Now it doesn't. This masks an issue with the roomserver where it can emit duplicate membership events. * Update joined_hosts_table.go --- federationsender/storage/postgres/joined_hosts_table.go | 2 +- federationsender/storage/sqlite3/joined_hosts_table.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/federationsender/storage/postgres/joined_hosts_table.go b/federationsender/storage/postgres/joined_hosts_table.go index 0bc9335dd..0c1e91eeb 100644 --- a/federationsender/storage/postgres/joined_hosts_table.go +++ b/federationsender/storage/postgres/joined_hosts_table.go @@ -48,7 +48,7 @@ CREATE INDEX IF NOT EXISTS federatonsender_joined_hosts_room_id_idx const insertJoinedHostsSQL = "" + "INSERT INTO federationsender_joined_hosts (room_id, event_id, server_name)" + - " VALUES ($1, $2, $3)" + " VALUES ($1, $2, $3) ON CONFLICT DO NOTHING" const deleteJoinedHostsSQL = "" + "DELETE FROM federationsender_joined_hosts WHERE event_id = ANY($1)" diff --git a/federationsender/storage/sqlite3/joined_hosts_table.go b/federationsender/storage/sqlite3/joined_hosts_table.go index 1f906808d..4c0c1f510 100644 --- a/federationsender/storage/sqlite3/joined_hosts_table.go +++ b/federationsender/storage/sqlite3/joined_hosts_table.go @@ -47,7 +47,7 @@ CREATE INDEX IF NOT EXISTS federatonsender_joined_hosts_room_id_idx ` const insertJoinedHostsSQL = "" + - "INSERT INTO federationsender_joined_hosts (room_id, event_id, server_name)" + + "INSERT OR IGNORE INTO federationsender_joined_hosts (room_id, event_id, server_name)" + " VALUES ($1, $2, $3)" const deleteJoinedHostsSQL = "" + From a9faa1bc4488412f42d4926ab76b35f53cd008cd Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 15 Apr 2021 09:58:26 +0100 Subject: [PATCH 13/14] Fix registration error when disabled --- clientapi/routing/register.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clientapi/routing/register.go b/clientapi/routing/register.go index 872bdd736..526418669 100644 --- a/clientapi/routing/register.go +++ b/clientapi/routing/register.go @@ -614,7 +614,10 @@ func handleRegistrationFlow( } if cfg.RegistrationDisabled && r.Auth.Type != authtypes.LoginTypeSharedSecret { - return util.MessageResponse(http.StatusForbidden, "Registration has been disabled") + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden("Registration is disabled"), + } } // Make sure normal user isn't registering under an exclusive application From d6e9b7b307ff0d7541046ec33890d49239c7a6ca Mon Sep 17 00:00:00 2001 From: Fero Date: Mon, 19 Apr 2021 17:29:51 +0200 Subject: [PATCH 14/14] Remove the 'Content-Type' request header requirement (#1834) --- mediaapi/routing/upload.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mediaapi/routing/upload.go b/mediaapi/routing/upload.go index a38b56e09..f1dd231df 100644 --- a/mediaapi/routing/upload.go +++ b/mediaapi/routing/upload.go @@ -237,13 +237,6 @@ func (r *uploadRequest) Validate(maxFileSizeBytes config.FileSizeBytes) *util.JS if maxFileSizeBytes > 0 && r.MediaMetadata.FileSizeBytes > types.FileSizeBytes(maxFileSizeBytes) { return requestEntityTooLargeJSONResponse(maxFileSizeBytes) } - // TODO: Check if the Content-Type is a valid type? - if r.MediaMetadata.ContentType == "" { - return &util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.Unknown("HTTP Content-Type request header must be set."), - } - } if strings.HasPrefix(string(r.MediaMetadata.UploadName), "~") { return &util.JSONResponse{ Code: http.StatusBadRequest,