From 8fb74fe99a968aa438d436bd251baf5a790e8156 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Oct 2020 10:37:52 +0100 Subject: Yggdrasil demo tweaks --- cmd/dendrite-demo-yggdrasil/yggconn/node.go | 10 ++++------ go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/node.go b/cmd/dendrite-demo-yggdrasil/yggconn/node.go index 9b123aa6..c036c0d8 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/node.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/node.go @@ -205,13 +205,11 @@ func (n *Node) SessionCount() int { func (n *Node) KnownNodes() []gomatrixserverlib.ServerName { nodemap := map[string]struct{}{ - "b5ae50589e50991dd9dd7d59c5c5f7a4521e8da5b603b7f57076272abc58b374": {}, + //"b5ae50589e50991dd9dd7d59c5c5f7a4521e8da5b603b7f57076272abc58b374": {}, + } + for _, peer := range n.core.GetSwitchPeers() { + nodemap[hex.EncodeToString(peer.SigPublicKey[:])] = struct{}{} } - /* - for _, peer := range n.core.GetSwitchPeers() { - nodemap[hex.EncodeToString(peer.PublicKey[:])] = struct{}{} - } - */ n.sessions.Range(func(_, v interface{}) bool { session, ok := v.(quic.Session) if !ok { diff --git a/go.mod b/go.mod index 5793897a..4a382d6d 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/tidwall/sjson v1.1.1 github.com/uber/jaeger-client-go v2.25.0+incompatible github.com/uber/jaeger-lib v2.2.0+incompatible - github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200806125501-cd4685a3b4de + github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20201006093556-760d9a7fd5ee go.uber.org/atomic v1.6.0 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a gopkg.in/h2non/bimg.v1 v1.1.4 diff --git a/go.sum b/go.sum index 20479015..adb0f0e8 100644 --- a/go.sum +++ b/go.sum @@ -851,8 +851,8 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855/go.mod h1:xQdsh08Io6nV4WRnOVTe6gI8/2iTvfLDQ0CYa5aMt+I= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200806125501-cd4685a3b4de h1:p91aw0Mvol825U+5bvV9BBPl+HQxIczj7wxIOxZs70M= -github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200806125501-cd4685a3b4de/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20201006093556-760d9a7fd5ee h1:Kot820OfxWfYrk5di5f4S5s0jXXrQj8w8BG5826HAv4= +github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20201006093556-760d9a7fd5ee/go.mod h1:d+Nz6SPeG6kmeSPFL0cvfWfgwEql75fUnZiAONgvyBE= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= -- cgit v1.2.3 From bf90db5b60d694d8af0e8ec1d90d2501604ab219 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Oct 2020 11:05:00 +0100 Subject: Remove KindRewrite (#1481) * Don't send rewrite events * Remove final traces of rewrite events * Remove test that is no longer needed * Revert "Remove test that is no longer needed" This reverts commit 9a45babff690480acd656a52f2c2950a5f7e9ada. * Update test to use KindOutlier --- federationsender/internal/perform.go | 2 +- roomserver/api/input.go | 4 -- roomserver/api/wrapper.go | 93 ------------------------------- roomserver/internal/input/input_events.go | 9 --- roomserver/roomserver_test.go | 4 +- 5 files changed, 3 insertions(+), 109 deletions(-) diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index 6aea296b..0c9dd257 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -232,7 +232,7 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( // If we successfully performed a send_join above then the other // server now thinks we're a part of the room. Send the newly // returned state to the roomserver to update our local view. - if err = roomserverAPI.SendEventWithRewrite( + if err = roomserverAPI.SendEventWithState( ctx, r.rsAPI, respState, event.Headered(respMakeJoin.RoomVersion), diff --git a/roomserver/api/input.go b/roomserver/api/input.go index 862a6fa1..a72e2d9a 100644 --- a/roomserver/api/input.go +++ b/roomserver/api/input.go @@ -35,10 +35,6 @@ const ( // KindBackfill event extend the contiguous graph going backwards. // They always have state. KindBackfill = 3 - // KindRewrite events are used when rewriting the head of the room - // graph with entirely new state. The output events generated will - // be state events rather than timeline events. - KindRewrite = 4 ) // DoNotSendToOtherServers tells us not to send the event to other matrix diff --git a/roomserver/api/wrapper.go b/roomserver/api/wrapper.go index 24949fc6..a38c00df 100644 --- a/roomserver/api/wrapper.go +++ b/roomserver/api/wrapper.go @@ -80,99 +80,6 @@ func SendEventWithState( return SendInputRoomEvents(ctx, rsAPI, ires) } -// SendEventWithRewrite writes an event with KindNew to the roomserver along -// with a number of rewrite and outlier events for state and auth events -// respectively. -func SendEventWithRewrite( - ctx context.Context, rsAPI RoomserverInternalAPI, state *gomatrixserverlib.RespState, - event gomatrixserverlib.HeaderedEvent, haveEventIDs map[string]bool, -) error { - isCurrentState := map[string]struct{}{} - for _, se := range state.StateEvents { - isCurrentState[se.EventID()] = struct{}{} - } - - authAndStateEvents, err := state.Events() - if err != nil { - return err - } - - var ires []InputRoomEvent - var stateIDs []string - - // This function generates three things: - // A - A set of "rewrite" events, which will form the newly rewritten - // state before the event, which includes every rewrite event that - // came before it in its state - // B - A set of "outlier" events, which are auth events but not part - // of the rewritten state - // C - A "new" event, which include all of the rewrite events in its - // state - for _, authOrStateEvent := range authAndStateEvents { - if authOrStateEvent.StateKey() == nil { - continue - } - if haveEventIDs[authOrStateEvent.EventID()] { - continue - } - if event.StateKey() == nil { - continue - } - - // We will handle an event as if it's an outlier if one of the - // following conditions is true: - storeAsOutlier := false - if _, ok := isCurrentState[authOrStateEvent.EventID()]; !ok { - // The event is an auth event and isn't a part of the state set. - // We'll send it as an outlier because we need it to be stored - // in case something is referring to it as an auth event. - storeAsOutlier = true - } - - if storeAsOutlier { - ires = append(ires, InputRoomEvent{ - Kind: KindOutlier, - Event: authOrStateEvent.Headered(event.RoomVersion), - AuthEventIDs: authOrStateEvent.AuthEventIDs(), - }) - continue - } - - // If the event isn't an outlier then we'll instead send it as a - // rewrite event, so that it'll form part of the rewritten state. - // These events will go through the membership and latest event - // updaters and we will generate output events, but they will be - // flagged as non-current (i.e. didn't just happen) events. - // Each of these rewrite events includes all of the rewrite events - // that came before in their StateEventIDs. - ires = append(ires, InputRoomEvent{ - Kind: KindRewrite, - Event: authOrStateEvent.Headered(event.RoomVersion), - AuthEventIDs: authOrStateEvent.AuthEventIDs(), - HasState: true, - StateEventIDs: stateIDs, - }) - - // Add the event ID into the StateEventIDs of all subsequent - // rewrite events, and the new event. - stateIDs = append(stateIDs, authOrStateEvent.EventID()) - } - - // Send the final event as a new event, which will generate - // a timeline output event for it. All of the rewrite events - // that came before will be sent as StateEventIDs, forming a - // new clean state before the event. - ires = append(ires, InputRoomEvent{ - Kind: KindNew, - Event: event, - AuthEventIDs: event.AuthEventIDs(), - HasState: true, - StateEventIDs: stateIDs, - }) - - return SendInputRoomEvents(ctx, rsAPI, ires) -} - // SendInputRoomEvents to the roomserver. func SendInputRoomEvents( ctx context.Context, rsAPI RoomserverInternalAPI, ires []InputRoomEvent, diff --git a/roomserver/internal/input/input_events.go b/roomserver/internal/input/input_events.go index 3d44f048..810d8cda 100644 --- a/roomserver/internal/input/input_events.go +++ b/roomserver/internal/input/input_events.go @@ -136,15 +136,6 @@ func (r *Inputer) processRoomEvent( return event.EventID(), rejectionErr } - if input.Kind == api.KindRewrite { - logrus.WithFields(logrus.Fields{ - "event_id": event.EventID(), - "type": event.Type(), - "room": event.RoomID(), - }).Debug("Stored rewrite") - return event.EventID(), nil - } - if err = r.updateLatestEvents( ctx, // context roomInfo, // room info for the room being updated diff --git a/roomserver/roomserver_test.go b/roomserver/roomserver_test.go index 912c5852..2a03195c 100644 --- a/roomserver/roomserver_test.go +++ b/roomserver/roomserver_test.go @@ -238,7 +238,7 @@ func TestOutputRedactedEvent(t *testing.T) { } } -// This tests that rewriting state via KindRewrite works correctly. +// This tests that rewriting state works correctly. // This creates a small room with a create/join/name state, then replays it // with a new room name. We expect the output events to contain the original events, // followed by a single OutputNewRoomEvent with RewritesState set to true with the @@ -344,7 +344,7 @@ func TestOutputRewritesState(t *testing.T) { for i := 0; i < len(rewriteEvents)-1; i++ { ev := rewriteEvents[i] inputEvents = append(inputEvents, api.InputRoomEvent{ - Kind: api.KindRewrite, + Kind: api.KindOutlier, Event: ev, AuthEventIDs: ev.AuthEventIDs(), HasState: true, -- cgit v1.2.3 From 1eaf7aa27e5e4592cd5f8d8c3d9c42cece798748 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Tue, 6 Oct 2020 11:05:15 +0100 Subject: Use [] not null when there are no devices (#1480) --- federationapi/routing/devices.go | 1 + 1 file changed, 1 insertion(+) diff --git a/federationapi/routing/devices.go b/federationapi/routing/devices.go index 00631b9b..07862451 100644 --- a/federationapi/routing/devices.go +++ b/federationapi/routing/devices.go @@ -40,6 +40,7 @@ func GetUserDevices( response := gomatrixserverlib.RespUserDevices{ UserID: userID, StreamID: res.StreamID, + Devices: []gomatrixserverlib.RespUserDevice{}, } for _, dev := range res.Devices { -- cgit v1.2.3 From d69eba10e5fb4cbe847776f1fbc6b96cbced3d66 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Oct 2020 14:51:32 +0100 Subject: Add furl (#1482) * Add furl * Add POST support --- cmd/furl/main.go | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 cmd/furl/main.go diff --git a/cmd/furl/main.go b/cmd/furl/main.go new file mode 100644 index 00000000..efaaa4b8 --- /dev/null +++ b/cmd/furl/main.go @@ -0,0 +1,123 @@ +package main + +import ( + "bufio" + "bytes" + "context" + "crypto/ed25519" + "encoding/json" + "encoding/pem" + "flag" + "fmt" + "io/ioutil" + "net/url" + "os" + + "github.com/matrix-org/gomatrixserverlib" +) + +var requestFrom = flag.String("from", "", "the server name that the request should originate from") +var requestKey = flag.String("key", "matrix_key.pem", "the private key to use when signing the request") +var requestPost = flag.Bool("post", false, "send a POST request instead of GET (pipe input into stdin or type followed by Ctrl-D)") + +func main() { + flag.Parse() + + if requestFrom == nil || *requestFrom == "" { + fmt.Println("expecting: furl -from origin.com [-key matrix_key.pem] https://path/to/url") + fmt.Println("supported flags:") + flag.PrintDefaults() + os.Exit(1) + } + + data, err := ioutil.ReadFile(*requestKey) + if err != nil { + panic(err) + } + + var privateKey ed25519.PrivateKey + keyBlock, _ := pem.Decode(data) + if keyBlock == nil { + panic("keyBlock is nil") + } + if keyBlock.Type == "MATRIX PRIVATE KEY" { + _, privateKey, err = ed25519.GenerateKey(bytes.NewReader(keyBlock.Bytes)) + if err != nil { + panic(err) + } + } else { + panic("unexpected key block") + } + + client := gomatrixserverlib.NewFederationClient( + gomatrixserverlib.ServerName(*requestFrom), + gomatrixserverlib.KeyID(keyBlock.Headers["Key-ID"]), + privateKey, + false, + ) + + u, err := url.Parse(flag.Arg(0)) + if err != nil { + panic(err) + } + + var bodyObj interface{} + var bodyBytes []byte + method := "GET" + if *requestPost { + method = "POST" + fmt.Println("Waiting for JSON input. Press Enter followed by Ctrl-D when done...") + + scan := bufio.NewScanner(os.Stdin) + for scan.Scan() { + bytes := scan.Bytes() + bodyBytes = append(bodyBytes, bytes...) + } + fmt.Println("Done!") + if err = json.Unmarshal(bodyBytes, &bodyObj); err != nil { + panic(err) + } + } + + req := gomatrixserverlib.NewFederationRequest( + method, + gomatrixserverlib.ServerName(u.Host), + u.RequestURI(), + ) + + if *requestPost { + if err = req.SetContent(bodyObj); err != nil { + panic(err) + } + } + + if err = req.Sign( + gomatrixserverlib.ServerName(*requestFrom), + gomatrixserverlib.KeyID(keyBlock.Headers["Key-ID"]), + privateKey, + ); err != nil { + panic(err) + } + + httpReq, err := req.HTTPRequest() + if err != nil { + panic(err) + } + + var res interface{} + err = client.DoRequestAndParseResponse( + context.TODO(), + httpReq, + &res, + ) + if err != nil { + panic(err) + } + + j, err := json.MarshalIndent(res, "", " ") + if err != nil { + panic(err) + } + + fmt.Println(string(j)) +} -- cgit v1.2.3 From ee79d662e764576e02e16a74c62349e81326c64a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Oct 2020 15:37:31 +0100 Subject: Update to matrix-org/gomatrixserverlib#233 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4a382d6d..c98aa61e 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4 github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd - github.com/matrix-org/gomatrixserverlib v0.0.0-20201005165532-90f2e192f625 + github.com/matrix-org/gomatrixserverlib v0.0.0-20201006143701-222e7423a5e3 github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91 github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 github.com/mattn/go-sqlite3 v1.14.2 diff --git a/go.sum b/go.sum index adb0f0e8..c92774bd 100644 --- a/go.sum +++ b/go.sum @@ -569,8 +569,8 @@ github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bh github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd h1:xVrqJK3xHREMNjwjljkAUaadalWc0rRbmVuQatzmgwg= github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= -github.com/matrix-org/gomatrixserverlib v0.0.0-20201005165532-90f2e192f625 h1:9ShgY0ZkfLzqe3gv18V5WxDAZ4dgUvJwnGORycox680= -github.com/matrix-org/gomatrixserverlib v0.0.0-20201005165532-90f2e192f625/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20201006143701-222e7423a5e3 h1:lWR/w6rXKZJJU1yGHb2zem/EK7+aYhUcRgAOiouZAxk= +github.com/matrix-org/gomatrixserverlib v0.0.0-20201006143701-222e7423a5e3/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91 h1:HJ6U3S3ljJqNffYMcIeAncp5qT/i+ZMiJ2JC2F0aXP4= github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91/go.mod h1:sjyPyRxKM5uw1nD2cJ6O2OxI6GOqyVBfNXqKjBZTBZE= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo= -- cgit v1.2.3 From 4feff8e8d9efd36b5d202ba219af997a8313866a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Oct 2020 17:59:08 +0100 Subject: Don't give up if we fail to fetch a key (#1483) * Don't give up if we fail to fetch a key * Fix logging line * furl nolint --- cmd/furl/main.go | 1 + roomserver/internal/perform/perform_backfill.go | 2 +- serverkeyapi/internal/api.go | 4 ---- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/cmd/furl/main.go b/cmd/furl/main.go index efaaa4b8..3955ef0c 100644 --- a/cmd/furl/main.go +++ b/cmd/furl/main.go @@ -20,6 +20,7 @@ var requestFrom = flag.String("from", "", "the server name that the request shou var requestKey = flag.String("key", "matrix_key.pem", "the private key to use when signing the request") var requestPost = flag.Bool("post", false, "send a POST request instead of GET (pipe input into stdin or type followed by Ctrl-D)") +// nolint:gocyclo func main() { flag.Parse() diff --git a/roomserver/internal/perform/perform_backfill.go b/roomserver/internal/perform/perform_backfill.go index eb1aa99b..f6091994 100644 --- a/roomserver/internal/perform/perform_backfill.go +++ b/roomserver/internal/perform/perform_backfill.go @@ -195,7 +195,7 @@ func (r *Backfiller) fetchAndStoreMissingEvents(ctx context.Context, roomVer gom logger.Infof("returned %d PDUs which made events %+v", len(res.PDUs), result) for _, res := range result { if res.Error != nil { - logger.WithError(err).Warn("event failed PDU checks") + logger.WithError(res.Error).Warn("event failed PDU checks") continue } missingMap[id] = res.Event diff --git a/serverkeyapi/internal/api.go b/serverkeyapi/internal/api.go index b8a36225..335bfe4c 100644 --- a/serverkeyapi/internal/api.go +++ b/serverkeyapi/internal/api.go @@ -98,10 +98,6 @@ func (s *ServerKeyAPI) FetchKeys( // we've failed to satisfy it from local keys, database keys or from // all of the fetchers. Report an error. logrus.Warnf("Failed to retrieve key %q for server %q", req.KeyID, req.ServerName) - return results, fmt.Errorf( - "server key API failed to satisfy key request for server %q key ID %q", - req.ServerName, req.KeyID, - ) } } -- cgit v1.2.3 From 0f7e707f399e7f633c58f4e1a5aedc0e45f90241 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Tue, 6 Oct 2020 18:09:02 +0100 Subject: Optimise servers to backfill from (#1485) - Prefer perspective servers if they are in the room. - Limit the number of backfill servers to 5 to avoid taking too long. --- roomserver/internal/api.go | 34 ++++++++++++--------- roomserver/internal/perform/perform_backfill.go | 39 ++++++++++++++++++++----- roomserver/roomserver.go | 7 ++++- 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go index 8dc1a170..ee4e4ec9 100644 --- a/roomserver/internal/api.go +++ b/roomserver/internal/api.go @@ -26,28 +26,30 @@ type RoomserverInternalAPI struct { *perform.Leaver *perform.Publisher *perform.Backfiller - DB storage.Database - Cfg *config.RoomServer - Producer sarama.SyncProducer - Cache caching.RoomServerCaches - ServerName gomatrixserverlib.ServerName - KeyRing gomatrixserverlib.JSONVerifier - fsAPI fsAPI.FederationSenderInternalAPI - OutputRoomEventTopic string // Kafka topic for new output room events + DB storage.Database + Cfg *config.RoomServer + Producer sarama.SyncProducer + Cache caching.RoomServerCaches + ServerName gomatrixserverlib.ServerName + KeyRing gomatrixserverlib.JSONVerifier + fsAPI fsAPI.FederationSenderInternalAPI + OutputRoomEventTopic string // Kafka topic for new output room events + PerspectiveServerNames []gomatrixserverlib.ServerName } func NewRoomserverAPI( cfg *config.RoomServer, roomserverDB storage.Database, producer sarama.SyncProducer, outputRoomEventTopic string, caches caching.RoomServerCaches, - keyRing gomatrixserverlib.JSONVerifier, + keyRing gomatrixserverlib.JSONVerifier, perspectiveServerNames []gomatrixserverlib.ServerName, ) *RoomserverInternalAPI { serverACLs := acls.NewServerACLs(roomserverDB) a := &RoomserverInternalAPI{ - DB: roomserverDB, - Cfg: cfg, - Cache: caches, - ServerName: cfg.Matrix.ServerName, - KeyRing: keyRing, + DB: roomserverDB, + Cfg: cfg, + Cache: caches, + ServerName: cfg.Matrix.ServerName, + PerspectiveServerNames: perspectiveServerNames, + KeyRing: keyRing, Queryer: &query.Queryer{ DB: roomserverDB, Cache: caches, @@ -105,6 +107,10 @@ func (r *RoomserverInternalAPI) SetFederationSenderAPI(fsAPI fsAPI.FederationSen DB: r.DB, FSAPI: r.fsAPI, KeyRing: r.KeyRing, + // Perspective servers are trusted to not lie about server keys, so we will also + // prefer these servers when backfilling (assuming they are in the room) rather + // than trying random servers + PreferServers: r.PerspectiveServerNames, } } diff --git a/roomserver/internal/perform/perform_backfill.go b/roomserver/internal/perform/perform_backfill.go index f6091994..d90ac8fc 100644 --- a/roomserver/internal/perform/perform_backfill.go +++ b/roomserver/internal/perform/perform_backfill.go @@ -30,11 +30,19 @@ import ( "github.com/sirupsen/logrus" ) +// the max number of servers to backfill from per request. If this is too low we may fail to backfill when +// we could've from another server. If this is too high we may take far too long to successfully backfill +// as we try dead servers. +const maxBackfillServers = 5 + type Backfiller struct { ServerName gomatrixserverlib.ServerName DB storage.Database FSAPI federationSenderAPI.FederationSenderInternalAPI KeyRing gomatrixserverlib.JSONVerifier + + // The servers which should be preferred above other servers when backfilling + PreferServers []gomatrixserverlib.ServerName } // PerformBackfill implements api.RoomServerQueryAPI @@ -96,7 +104,7 @@ func (r *Backfiller) backfillViaFederation(ctx context.Context, req *api.Perform if info == nil || info.IsStub { return fmt.Errorf("backfillViaFederation: missing room info for room %s", req.RoomID) } - requester := newBackfillRequester(r.DB, r.FSAPI, r.ServerName, req.BackwardsExtremities) + requester := newBackfillRequester(r.DB, r.FSAPI, r.ServerName, req.BackwardsExtremities, r.PreferServers) // Request 100 items regardless of what the query asks for. // We don't want to go much higher than this. // We can't honour exactly the limit as some sytests rely on requesting more for tests to pass @@ -215,10 +223,11 @@ func (r *Backfiller) fetchAndStoreMissingEvents(ctx context.Context, roomVer gom // backfillRequester implements gomatrixserverlib.BackfillRequester type backfillRequester struct { - db storage.Database - fsAPI federationSenderAPI.FederationSenderInternalAPI - thisServer gomatrixserverlib.ServerName - bwExtrems map[string][]string + db storage.Database + fsAPI federationSenderAPI.FederationSenderInternalAPI + thisServer gomatrixserverlib.ServerName + preferServer map[gomatrixserverlib.ServerName]bool + bwExtrems map[string][]string // per-request state servers []gomatrixserverlib.ServerName @@ -226,7 +235,14 @@ type backfillRequester struct { eventIDMap map[string]gomatrixserverlib.Event } -func newBackfillRequester(db storage.Database, fsAPI federationSenderAPI.FederationSenderInternalAPI, thisServer gomatrixserverlib.ServerName, bwExtrems map[string][]string) *backfillRequester { +func newBackfillRequester( + db storage.Database, fsAPI federationSenderAPI.FederationSenderInternalAPI, thisServer gomatrixserverlib.ServerName, + bwExtrems map[string][]string, preferServers []gomatrixserverlib.ServerName, +) *backfillRequester { + preferServer := make(map[gomatrixserverlib.ServerName]bool) + for _, p := range preferServers { + preferServer[p] = true + } return &backfillRequester{ db: db, fsAPI: fsAPI, @@ -234,6 +250,7 @@ func newBackfillRequester(db storage.Database, fsAPI federationSenderAPI.Federat eventIDToBeforeStateIDs: make(map[string][]string), eventIDMap: make(map[string]gomatrixserverlib.Event), bwExtrems: bwExtrems, + preferServer: preferServer, } } @@ -436,8 +453,16 @@ FindSuccessor: if server == b.thisServer { continue } - servers = append(servers, server) + if b.preferServer[server] { // insert at the front + servers = append([]gomatrixserverlib.ServerName{server}, servers...) + } else { // insert at the back + servers = append(servers, server) + } + } + if len(servers) > maxBackfillServers { + servers = servers[:maxBackfillServers] } + b.servers = servers return servers } diff --git a/roomserver/roomserver.go b/roomserver/roomserver.go index 2eabf450..98a86e5b 100644 --- a/roomserver/roomserver.go +++ b/roomserver/roomserver.go @@ -41,6 +41,11 @@ func NewInternalAPI( ) api.RoomserverInternalAPI { cfg := &base.Cfg.RoomServer + var perspectiveServerNames []gomatrixserverlib.ServerName + for _, kp := range base.Cfg.ServerKeyAPI.KeyPerspectives { + perspectiveServerNames = append(perspectiveServerNames, kp.ServerName) + } + roomserverDB, err := storage.Open(&cfg.Database, base.Caches) if err != nil { logrus.WithError(err).Panicf("failed to connect to room server db") @@ -48,6 +53,6 @@ func NewInternalAPI( return internal.NewRoomserverAPI( cfg, roomserverDB, base.KafkaProducer, string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputRoomEvent)), - base.Caches, keyRing, + base.Caches, keyRing, perspectiveServerNames, ) } -- cgit v1.2.3 From f7c15071decd9a33fabece54b86e92e10009a034 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Oct 2020 10:30:27 +0100 Subject: Don't return 500s on checking to see if a remote server is allowed to see an event we don't know about (#1490) --- roomserver/internal/helpers/helpers.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/roomserver/internal/helpers/helpers.go b/roomserver/internal/helpers/helpers.go index b7e6ce86..a2fbd287 100644 --- a/roomserver/internal/helpers/helpers.go +++ b/roomserver/internal/helpers/helpers.go @@ -2,6 +2,8 @@ package helpers import ( "context" + "database/sql" + "errors" "fmt" "github.com/matrix-org/dendrite/roomserver/api" @@ -217,6 +219,9 @@ func CheckServerAllowedToSeeEvent( roomState := state.NewStateResolution(db, info) stateEntries, err := roomState.LoadStateAtEvent(ctx, eventID) if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } return false, err } -- cgit v1.2.3 From d821f9d3c92adde5b0576de03d0d44ffce5f0182 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Oct 2020 14:05:33 +0100 Subject: Deep checking of forward extremities (#1491) * Deep forward extremity calculation * Use updater txn * Update error * Update error * Create previous event references in StoreEvent * Use latest events updater to row-lock prev events * Fix unexpected fallthrough * Fix deadlock * Don't roll back * Update comments in calculateLatest * Don't include events that we can't find references for in the forward extremities * Add another passing test --- roomserver/api/input.go | 3 - roomserver/internal/input/input_events.go | 2 +- roomserver/internal/input/input_latest_events.go | 118 ++++++++++------------- roomserver/storage/shared/storage.go | 28 +++++- sytest-whitelist | 4 +- 5 files changed, 81 insertions(+), 74 deletions(-) diff --git a/roomserver/api/input.go b/roomserver/api/input.go index a72e2d9a..dd693203 100644 --- a/roomserver/api/input.go +++ b/roomserver/api/input.go @@ -32,9 +32,6 @@ const ( // there was a new event that references an event that we don't // have a copy of. KindNew = 2 - // KindBackfill event extend the contiguous graph going backwards. - // They always have state. - KindBackfill = 3 ) // DoNotSendToOtherServers tells us not to send the event to other matrix diff --git a/roomserver/internal/input/input_events.go b/roomserver/internal/input/input_events.go index 810d8cda..11334159 100644 --- a/roomserver/internal/input/input_events.go +++ b/roomserver/internal/input/input_events.go @@ -54,7 +54,7 @@ func (r *Inputer) processRoomEvent( } var softfail bool - if input.Kind == api.KindBackfill || input.Kind == api.KindNew { + if input.Kind == api.KindNew { // Check that the event passes authentication checks based on the // current room state. softfail, err = helpers.CheckForSoftFail(ctx, r.DB, headered, input.StateEventIDs) diff --git a/roomserver/internal/input/input_latest_events.go b/roomserver/internal/input/input_latest_events.go index 2e9f3b4e..7be6372b 100644 --- a/roomserver/internal/input/input_latest_events.go +++ b/roomserver/internal/input/input_latest_events.go @@ -28,6 +28,7 @@ import ( "github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" + "github.com/sirupsen/logrus" ) // updateLatestEvents updates the list of latest events for this room in the database and writes the @@ -116,7 +117,6 @@ type latestEventsUpdater struct { } func (u *latestEventsUpdater) doUpdateLatestEvents() error { - prevEvents := u.event.PrevEvents() u.lastEventIDSent = u.updater.LastEventIDSent() u.oldStateNID = u.updater.CurrentStateSnapshotNID() @@ -140,30 +140,12 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error { return nil } - // Update the roomserver_previous_events table with references. This - // is effectively tracking the structure of the DAG. - if err = u.updater.StorePreviousEvents(u.stateAtEvent.EventNID, prevEvents); err != nil { - return fmt.Errorf("u.updater.StorePreviousEvents: %w", err) - } - - // Get the event reference for our new event. This will be used when - // determining if the event is referenced by an existing event. - eventReference := u.event.EventReference() - - // Check if our new event is already referenced by an existing event - // in the room. If it is then it isn't a latest event. - alreadyReferenced, err := u.updater.IsReferenced(eventReference) - if err != nil { - return fmt.Errorf("u.updater.IsReferenced: %w", err) - } - - // Work out what the latest events are. - u.latest = calculateLatest( + // Work out what the latest events are. This will include the new + // event if it is not already referenced. + u.calculateLatest( oldLatest, - alreadyReferenced, - prevEvents, types.StateAtEventAndReference{ - EventReference: eventReference, + EventReference: u.event.EventReference(), StateAtEvent: u.stateAtEvent, }, ) @@ -215,27 +197,12 @@ func (u *latestEventsUpdater) latestState() error { var err error roomState := state.NewStateResolution(u.api.DB, *u.roomInfo) - // Get a list of the current room state events if available. - var currentState []types.StateEntry - if u.roomInfo.StateSnapshotNID != 0 { - currentState, _ = roomState.LoadStateAtSnapshot(u.ctx, u.roomInfo.StateSnapshotNID) - } - - // Get a list of the current latest events. This will include both - // the current room state and the latest events after the input event. - // The idea is that we will perform state resolution on this set and - // any conflicting events will be resolved properly. - latestStateAtEvents := make([]types.StateAtEvent, len(u.latest)+len(currentState)) - offset := 0 - for i := range currentState { - latestStateAtEvents[i] = types.StateAtEvent{ - BeforeStateSnapshotNID: u.roomInfo.StateSnapshotNID, - StateEntry: currentState[i], - } - offset++ - } + // Get a list of the current latest events. This may or may not + // include the new event from the input path, depending on whether + // it is a forward extremity or not. + latestStateAtEvents := make([]types.StateAtEvent, len(u.latest)) for i := range u.latest { - latestStateAtEvents[offset+i] = u.latest[i].StateAtEvent + latestStateAtEvents[i] = u.latest[i].StateAtEvent } // Takes the NIDs of the latest events and creates a state snapshot @@ -266,6 +233,14 @@ func (u *latestEventsUpdater) latestState() error { if err != nil { return fmt.Errorf("roomState.DifferenceBetweenStateSnapshots: %w", err) } + if len(u.removed) > len(u.added) { + // This really shouldn't happen. + // TODO: What is ultimately the best way to handle this situation? + return fmt.Errorf( + "invalid state delta wants to remove %d state but only add %d state (between state snapshots %d and %d)", + len(u.removed), len(u.added), u.oldStateNID, u.newStateNID, + ) + } // Also work out the state before the event removes and the event // adds. @@ -279,42 +254,49 @@ func (u *latestEventsUpdater) latestState() error { return nil } -func calculateLatest( +func (u *latestEventsUpdater) calculateLatest( oldLatest []types.StateAtEventAndReference, - alreadyReferenced bool, - prevEvents []gomatrixserverlib.EventReference, newEvent types.StateAtEventAndReference, -) []types.StateAtEventAndReference { - var alreadyInLatest bool +) { var newLatest []types.StateAtEventAndReference + + // First of all, let's see if any of the existing forward extremities + // now have entries in the previous events table. If they do then we + // will no longer include them as forward extremities. for _, l := range oldLatest { - keep := true - for _, prevEvent := range prevEvents { - if l.EventID == prevEvent.EventID && bytes.Equal(l.EventSHA256, prevEvent.EventSHA256) { - // This event can be removed from the latest events cause we've found an event that references it. - // (If an event is referenced by another event then it can't be one of the latest events in the room - // because we have an event that comes after it) - keep = false - break - } - } - if l.EventNID == newEvent.EventNID { - alreadyInLatest = true - } - if keep { - // Keep the event in the latest events. + referenced, err := u.updater.IsReferenced(l.EventReference) + if err != nil { + logrus.WithError(err).Errorf("Failed to retrieve event reference for %q", l.EventID) + } else if !referenced { newLatest = append(newLatest, l) } } - if !alreadyReferenced && !alreadyInLatest { - // This event is not referenced by any of the events in the room - // and the event is not already in the latest events. - // Add it to the latest events + // Then check and see if our new event is already included in that set. + // This ordinarily won't happen but it covers the edge-case that we've + // already seen this event before and it's a forward extremity, so rather + // than adding a duplicate, we'll just return the set as complete. + for _, l := range newLatest { + if l.EventReference.EventID == newEvent.EventReference.EventID && bytes.Equal(l.EventReference.EventSHA256, newEvent.EventReference.EventSHA256) { + // We've already referenced this new event so we can just return + // the newly completed extremities at this point. + u.latest = newLatest + return + } + } + + // At this point we've processed the old extremities, and we've checked + // that our new event isn't already in that set. Therefore now we can + // check if our *new* event is a forward extremity, and if it is, add + // it in. + referenced, err := u.updater.IsReferenced(newEvent.EventReference) + if err != nil { + logrus.WithError(err).Errorf("Failed to retrieve event reference for %q", newEvent.EventReference.EventID) + } else if !referenced { newLatest = append(newLatest, newEvent) } - return newLatest + u.latest = newLatest } func (u *latestEventsUpdater) makeOutputNewRoomEvent() (*api.OutputEvent, error) { diff --git a/roomserver/storage/shared/storage.go b/roomserver/storage/shared/storage.go index f8e733ab..e96eab71 100644 --- a/roomserver/storage/shared/storage.go +++ b/roomserver/storage/shared/storage.go @@ -474,6 +474,32 @@ func (d *Database) StoreEvent( return 0, types.StateAtEvent{}, nil, "", fmt.Errorf("d.Writer.Do: %w", err) } + // We should attempt to update the previous events table with any + // references that this new event makes. We do this using a latest + // events updater because it somewhat works as a mutex, ensuring + // that there's a row-level lock on the latest room events (well, + // on Postgres at least). + var roomInfo *types.RoomInfo + var updater *LatestEventsUpdater + if prevEvents := event.PrevEvents(); len(prevEvents) > 0 { + roomInfo, err = d.RoomInfo(ctx, event.RoomID()) + if err != nil { + return 0, types.StateAtEvent{}, nil, "", fmt.Errorf("d.RoomInfo: %w", err) + } + if roomInfo == nil && len(prevEvents) > 0 { + return 0, types.StateAtEvent{}, nil, "", fmt.Errorf("expected room %q to exist", event.RoomID()) + } + updater, err = d.GetLatestEventsForUpdate(ctx, *roomInfo) + if err != nil { + return 0, types.StateAtEvent{}, nil, "", fmt.Errorf("NewLatestEventsUpdater: %w", err) + } + if err = updater.StorePreviousEvents(eventNID, prevEvents); err != nil { + return 0, types.StateAtEvent{}, nil, "", fmt.Errorf("updater.StorePreviousEvents: %w", err) + } + succeeded := true + err = sqlutil.EndTransaction(updater, &succeeded) + } + return roomNID, types.StateAtEvent{ BeforeStateSnapshotNID: stateNID, StateEntry: types.StateEntry{ @@ -483,7 +509,7 @@ func (d *Database) StoreEvent( }, EventNID: eventNID, }, - }, redactionEvent, redactedEventID, nil + }, redactionEvent, redactedEventID, err } func (d *Database) PublishRoom(ctx context.Context, roomID string, publish bool) error { diff --git a/sytest-whitelist b/sytest-whitelist index 9a013cbf..e0f1f311 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -473,4 +473,6 @@ Inbound federation rejects invites which include invalid JSON for room version 6 Inbound federation rejects invite rejections which include invalid JSON for room version 6 GET /capabilities is present and well formed for registered user m.room.history_visibility == "joined" allows/forbids appropriately for Guest users -m.room.history_visibility == "joined" allows/forbids appropriately for Real users \ No newline at end of file +m.room.history_visibility == "joined" allows/forbids appropriately for Real users +Users cannot kick users who have already left a room +A prev_batch token from incremental sync can be used in the v1 messages API -- cgit v1.2.3 From 533006141ecbe18fc82d63a400cea57def8791d8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Oct 2020 15:29:14 +0100 Subject: Return 200 on join before time out (#1493) * Return 200 on join afer 15 seconds if nothing better has happened by that point * Return 202 instead, 20 second timeout --- clientapi/routing/joinroom.go | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/clientapi/routing/joinroom.go b/clientapi/routing/joinroom.go index c1011357..578aaec5 100644 --- a/clientapi/routing/joinroom.go +++ b/clientapi/routing/joinroom.go @@ -16,9 +16,11 @@ package routing import ( "net/http" + "time" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/httputil" + "github.com/matrix-org/dendrite/clientapi/jsonerror" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/storage/accounts" @@ -74,16 +76,32 @@ func JoinRoomByIDOrAlias( } // Ask the roomserver to perform the join. - rsAPI.PerformJoin(req.Context(), &joinReq, &joinRes) - if joinRes.Error != nil { - return joinRes.Error.JSONResponse() - } + done := make(chan util.JSONResponse, 1) + go func() { + defer close(done) + rsAPI.PerformJoin(req.Context(), &joinReq, &joinRes) + if joinRes.Error != nil { + done <- joinRes.Error.JSONResponse() + } else { + done <- util.JSONResponse{ + Code: http.StatusOK, + // TODO: Put the response struct somewhere internal. + JSON: struct { + RoomID string `json:"room_id"` + }{joinRes.RoomID}, + } + } + }() - return util.JSONResponse{ - Code: http.StatusOK, - // TODO: Put the response struct somewhere internal. - JSON: struct { - RoomID string `json:"room_id"` - }{joinRes.RoomID}, + // Wait either for the join to finish, or for us to hit a reasonable + // timeout, at which point we'll just return a 200 to placate clients. + select { + case <-time.After(time.Second * 20): + return util.JSONResponse{ + Code: http.StatusAccepted, + JSON: jsonerror.Unknown("The room join will continue in the background."), + } + case result := <-done: + return result } } -- cgit v1.2.3 From bf7e85848bce3ec9ef89e485699d1c5fc6b34e6b Mon Sep 17 00:00:00 2001 From: Kegsay Date: Wed, 7 Oct 2020 16:23:18 +0100 Subject: Rename serverkeyapi to signingkeyserver (#1492) * Rename serverkeyapi to signingkeyserver We use "api" for public facing stuff and "server" for internal stuff. As the server key API is internal only, we call it 'signing key server', which also clarifies the type of key (as opposed to TLS keys, E2E keys, etc) * Convert docker/scripts to use signing-key-server * Rename missed bits --- build/docker/config/dendrite-config.yaml | 6 +- build/docker/docker-compose.polylith.yml | 6 +- build/docker/images-build.sh | 2 +- build/docker/images-pull.sh | 1 + build/docker/images-push.sh | 2 +- build/docker/postgres/create_db.sh | 2 +- build/gobind/monolith.go | 2 +- cmd/dendrite-demo-libp2p/main.go | 8 +- cmd/dendrite-demo-yggdrasil/main.go | 2 +- cmd/dendrite-federation-api-server/main.go | 2 +- cmd/dendrite-federation-sender-server/main.go | 2 +- cmd/dendrite-monolith-server/main.go | 16 +- cmd/dendrite-room-server/main.go | 2 +- cmd/dendrite-server-key-api-server/main.go | 37 --- cmd/dendrite-signing-key-server/main.go | 37 +++ cmd/dendritejs/main.go | 2 +- cmd/generate-config/main.go | 2 +- dendrite-config.yaml | 6 +- docs/INSTALL.md | 2 +- internal/config/config.go | 16 +- internal/config/config_serverkey.go | 52 ---- internal/config/config_signingkeyserver.go | 52 ++++ internal/setup/base.go | 14 +- internal/setup/monolith.go | 4 +- internal/test/config.go | 6 +- roomserver/roomserver.go | 2 +- serverkeyapi/api/api.go | 40 --- serverkeyapi/internal/api.go | 270 ----------------- serverkeyapi/inthttp/client.go | 132 --------- serverkeyapi/inthttp/server.go | 43 --- serverkeyapi/serverkeyapi.go | 107 ------- serverkeyapi/serverkeyapi_test.go | 318 --------------------- serverkeyapi/storage/cache/keydb.go | 68 ----- serverkeyapi/storage/interface.go | 13 - serverkeyapi/storage/keydb.go | 45 --- serverkeyapi/storage/keydb_wasm.go | 50 ---- serverkeyapi/storage/postgres/keydb.go | 91 ------ serverkeyapi/storage/postgres/server_key_table.go | 143 --------- serverkeyapi/storage/sqlite3/keydb.go | 99 ------- serverkeyapi/storage/sqlite3/server_key_table.go | 159 ----------- signingkeyserver/api/api.go | 40 +++ signingkeyserver/internal/api.go | 270 +++++++++++++++++ signingkeyserver/inthttp/client.go | 132 +++++++++ signingkeyserver/inthttp/server.go | 43 +++ signingkeyserver/serverkeyapi_test.go | 318 +++++++++++++++++++++ signingkeyserver/signingkeyserver.go | 107 +++++++ signingkeyserver/storage/cache/keydb.go | 68 +++++ signingkeyserver/storage/interface.go | 13 + signingkeyserver/storage/keydb.go | 45 +++ signingkeyserver/storage/keydb_wasm.go | 50 ++++ signingkeyserver/storage/postgres/keydb.go | 91 ++++++ .../storage/postgres/server_key_table.go | 143 +++++++++ signingkeyserver/storage/sqlite3/keydb.go | 99 +++++++ .../storage/sqlite3/server_key_table.go | 159 +++++++++++ 54 files changed, 1721 insertions(+), 1720 deletions(-) delete mode 100644 cmd/dendrite-server-key-api-server/main.go create mode 100644 cmd/dendrite-signing-key-server/main.go delete mode 100644 internal/config/config_serverkey.go create mode 100644 internal/config/config_signingkeyserver.go delete mode 100644 serverkeyapi/api/api.go delete mode 100644 serverkeyapi/internal/api.go delete mode 100644 serverkeyapi/inthttp/client.go delete mode 100644 serverkeyapi/inthttp/server.go delete mode 100644 serverkeyapi/serverkeyapi.go delete mode 100644 serverkeyapi/serverkeyapi_test.go delete mode 100644 serverkeyapi/storage/cache/keydb.go delete mode 100644 serverkeyapi/storage/interface.go delete mode 100644 serverkeyapi/storage/keydb.go delete mode 100644 serverkeyapi/storage/keydb_wasm.go delete mode 100644 serverkeyapi/storage/postgres/keydb.go delete mode 100644 serverkeyapi/storage/postgres/server_key_table.go delete mode 100644 serverkeyapi/storage/sqlite3/keydb.go delete mode 100644 serverkeyapi/storage/sqlite3/server_key_table.go create mode 100644 signingkeyserver/api/api.go create mode 100644 signingkeyserver/internal/api.go create mode 100644 signingkeyserver/inthttp/client.go create mode 100644 signingkeyserver/inthttp/server.go create mode 100644 signingkeyserver/serverkeyapi_test.go create mode 100644 signingkeyserver/signingkeyserver.go create mode 100644 signingkeyserver/storage/cache/keydb.go create mode 100644 signingkeyserver/storage/interface.go create mode 100644 signingkeyserver/storage/keydb.go create mode 100644 signingkeyserver/storage/keydb_wasm.go create mode 100644 signingkeyserver/storage/postgres/keydb.go create mode 100644 signingkeyserver/storage/postgres/server_key_table.go create mode 100644 signingkeyserver/storage/sqlite3/keydb.go create mode 100644 signingkeyserver/storage/sqlite3/server_key_table.go diff --git a/build/docker/config/dendrite-config.yaml b/build/docker/config/dendrite-config.yaml index 2bf8dd85..2dc2f3b7 100644 --- a/build/docker/config/dendrite-config.yaml +++ b/build/docker/config/dendrite-config.yaml @@ -253,12 +253,12 @@ room_server: conn_max_lifetime: -1 # Configuration for the Server Key API (for server signing keys). -server_key_api: +signing_key_server: internal_api: listen: http://0.0.0.0:7780 - connect: http://server_key_api:7780 + connect: http://signing_key_server:7780 database: - connection_string: postgresql://dendrite:itsasecret@postgres/dendrite_serverkey?sslmode=disable + connection_string: postgresql://dendrite:itsasecret@postgres/dendrite_signingkeyserver?sslmode=disable max_open_conns: 100 max_idle_conns: 2 conn_max_lifetime: -1 diff --git a/build/docker/docker-compose.polylith.yml b/build/docker/docker-compose.polylith.yml index 6dd74314..8a4c50e0 100644 --- a/build/docker/docker-compose.polylith.yml +++ b/build/docker/docker-compose.polylith.yml @@ -128,9 +128,9 @@ services: networks: - internal - server_key_api: - hostname: server_key_api - image: matrixdotorg/dendrite:serverkeyapi + signing_key_server: + hostname: signing_key_server + image: matrixdotorg/dendrite:signingkeyserver command: [ "--config=dendrite.yaml" ] diff --git a/build/docker/images-build.sh b/build/docker/images-build.sh index fdff5132..d72bac21 100755 --- a/build/docker/images-build.sh +++ b/build/docker/images-build.sh @@ -17,5 +17,5 @@ docker build -t matrixdotorg/dendrite:keyserver --build-arg component=de docker build -t matrixdotorg/dendrite:mediaapi --build-arg component=dendrite-media-api-server -f build/docker/Dockerfile.component . docker build -t matrixdotorg/dendrite:roomserver --build-arg component=dendrite-room-server -f build/docker/Dockerfile.component . docker build -t matrixdotorg/dendrite:syncapi --build-arg component=dendrite-sync-api-server -f build/docker/Dockerfile.component . -docker build -t matrixdotorg/dendrite:serverkeyapi --build-arg component=dendrite-server-key-api-server -f build/docker/Dockerfile.component . +docker build -t matrixdotorg/dendrite:signingkeyserver --build-arg component=dendrite-signing-key-server -f build/docker/Dockerfile.component . docker build -t matrixdotorg/dendrite:userapi --build-arg component=dendrite-user-api-server -f build/docker/Dockerfile.component . diff --git a/build/docker/images-pull.sh b/build/docker/images-pull.sh index c6b09b6a..be918546 100755 --- a/build/docker/images-pull.sh +++ b/build/docker/images-pull.sh @@ -13,4 +13,5 @@ docker pull matrixdotorg/dendrite:keyserver docker pull matrixdotorg/dendrite:mediaapi docker pull matrixdotorg/dendrite:roomserver docker pull matrixdotorg/dendrite:syncapi +docker pull matrixdotorg/dendrite:signingkeyserver docker pull matrixdotorg/dendrite:userapi diff --git a/build/docker/images-push.sh b/build/docker/images-push.sh index 4838c76f..64920171 100755 --- a/build/docker/images-push.sh +++ b/build/docker/images-push.sh @@ -13,5 +13,5 @@ docker push matrixdotorg/dendrite:keyserver docker push matrixdotorg/dendrite:mediaapi docker push matrixdotorg/dendrite:roomserver docker push matrixdotorg/dendrite:syncapi -docker push matrixdotorg/dendrite:serverkeyapi +docker push matrixdotorg/dendrite:signingkeyserver docker push matrixdotorg/dendrite:userapi diff --git a/build/docker/postgres/create_db.sh b/build/docker/postgres/create_db.sh index 70d6743e..f8ee715a 100644 --- a/build/docker/postgres/create_db.sh +++ b/build/docker/postgres/create_db.sh @@ -1,5 +1,5 @@ #!/bin/bash -for db in account device mediaapi syncapi roomserver serverkey keyserver federationsender appservice e2ekey naffka; do +for db in account device mediaapi syncapi roomserver signingkeyserver keyserver federationsender appservice e2ekey naffka; do createdb -U dendrite -O dendrite dendrite_$db done diff --git a/build/gobind/monolith.go b/build/gobind/monolith.go index b4740ed4..7d10b87e 100644 --- a/build/gobind/monolith.go +++ b/build/gobind/monolith.go @@ -94,7 +94,7 @@ func (m *DendriteMonolith) Start() { cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-mediaapi.db", m.StorageDirectory)) cfg.SyncAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-syncapi.db", m.StorageDirectory)) cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-roomserver.db", m.StorageDirectory)) - cfg.ServerKeyAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-serverkey.db", m.StorageDirectory)) + cfg.SigningKeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-signingkeyserver.db", m.StorageDirectory)) cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-keyserver.db", m.StorageDirectory)) cfg.FederationSender.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-federationsender.db", m.StorageDirectory)) cfg.AppServiceAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-appservice.db", m.StorageDirectory)) diff --git a/cmd/dendrite-demo-libp2p/main.go b/cmd/dendrite-demo-libp2p/main.go index 1f674886..0f30e8d3 100644 --- a/cmd/dendrite-demo-libp2p/main.go +++ b/cmd/dendrite-demo-libp2p/main.go @@ -36,7 +36,7 @@ import ( "github.com/matrix-org/dendrite/internal/setup" "github.com/matrix-org/dendrite/keyserver" "github.com/matrix-org/dendrite/roomserver" - "github.com/matrix-org/dendrite/serverkeyapi" + "github.com/matrix-org/dendrite/signingkeyserver" "github.com/matrix-org/dendrite/userapi" "github.com/matrix-org/gomatrixserverlib" @@ -125,7 +125,7 @@ func main() { cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mediaapi.db", *instanceName)) cfg.SyncAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-syncapi.db", *instanceName)) cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-roomserver.db", *instanceName)) - cfg.ServerKeyAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-serverkey.db", *instanceName)) + cfg.SigningKeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-signingkeyserver.db", *instanceName)) cfg.FederationSender.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-federationsender.db", *instanceName)) cfg.AppServiceAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-appservice.db", *instanceName)) cfg.Global.Kafka.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-naffka.db", *instanceName)) @@ -143,8 +143,8 @@ func main() { userAPI := userapi.NewInternalAPI(accountDB, &cfg.UserAPI, nil, keyAPI) keyAPI.SetUserAPI(userAPI) - serverKeyAPI := serverkeyapi.NewInternalAPI( - &base.Base.Cfg.ServerKeyAPI, federation, base.Base.Caches, + serverKeyAPI := signingkeyserver.NewInternalAPI( + &base.Base.Cfg.SigningKeyServer, federation, base.Base.Caches, ) keyRing := serverKeyAPI.KeyRing() createKeyDB( diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index 257ddb58..5e8b9231 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -78,7 +78,7 @@ func main() { cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mediaapi.db", *instanceName)) cfg.SyncAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-syncapi.db", *instanceName)) cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-roomserver.db", *instanceName)) - cfg.ServerKeyAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-serverkey.db", *instanceName)) + cfg.SigningKeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-signingkeyserver.db", *instanceName)) cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-keyserver.db", *instanceName)) cfg.FederationSender.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-federationsender.db", *instanceName)) cfg.AppServiceAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-appservice.db", *instanceName)) diff --git a/cmd/dendrite-federation-api-server/main.go b/cmd/dendrite-federation-api-server/main.go index cab304e6..3ebb16f4 100644 --- a/cmd/dendrite-federation-api-server/main.go +++ b/cmd/dendrite-federation-api-server/main.go @@ -26,7 +26,7 @@ func main() { userAPI := base.UserAPIClient() federation := base.CreateFederationClient() - serverKeyAPI := base.ServerKeyAPIClient() + serverKeyAPI := base.SigningKeyServerHTTPClient() keyRing := serverKeyAPI.KeyRing() fsAPI := base.FederationSenderHTTPClient() rsAPI := base.RoomserverHTTPClient() diff --git a/cmd/dendrite-federation-sender-server/main.go b/cmd/dendrite-federation-sender-server/main.go index 4d918f6b..07380bb0 100644 --- a/cmd/dendrite-federation-sender-server/main.go +++ b/cmd/dendrite-federation-sender-server/main.go @@ -26,7 +26,7 @@ func main() { federation := base.CreateFederationClient() - serverKeyAPI := base.ServerKeyAPIClient() + serverKeyAPI := base.SigningKeyServerHTTPClient() keyRing := serverKeyAPI.KeyRing() rsAPI := base.RoomserverHTTPClient() diff --git a/cmd/dendrite-monolith-server/main.go b/cmd/dendrite-monolith-server/main.go index 28a349a7..c50c0c21 100644 --- a/cmd/dendrite-monolith-server/main.go +++ b/cmd/dendrite-monolith-server/main.go @@ -27,7 +27,7 @@ import ( "github.com/matrix-org/dendrite/keyserver" "github.com/matrix-org/dendrite/roomserver" "github.com/matrix-org/dendrite/roomserver/api" - "github.com/matrix-org/dendrite/serverkeyapi" + "github.com/matrix-org/dendrite/signingkeyserver" "github.com/matrix-org/dendrite/userapi" ) @@ -58,7 +58,7 @@ func main() { cfg.KeyServer.InternalAPI.Connect = httpAddr cfg.MediaAPI.InternalAPI.Connect = httpAddr cfg.RoomServer.InternalAPI.Connect = httpAddr - cfg.ServerKeyAPI.InternalAPI.Connect = httpAddr + cfg.SigningKeyServer.InternalAPI.Connect = httpAddr cfg.SyncAPI.InternalAPI.Connect = httpAddr } @@ -68,14 +68,14 @@ func main() { accountDB := base.CreateAccountsDB() federation := base.CreateFederationClient() - serverKeyAPI := serverkeyapi.NewInternalAPI( - &base.Cfg.ServerKeyAPI, federation, base.Caches, + skAPI := signingkeyserver.NewInternalAPI( + &base.Cfg.SigningKeyServer, federation, base.Caches, ) if base.UseHTTPAPIs { - serverkeyapi.AddInternalRoutes(base.InternalAPIMux, serverKeyAPI, base.Caches) - serverKeyAPI = base.ServerKeyAPIClient() + signingkeyserver.AddInternalRoutes(base.InternalAPIMux, skAPI, base.Caches) + skAPI = base.SigningKeyServerHTTPClient() } - keyRing := serverKeyAPI.KeyRing() + keyRing := skAPI.KeyRing() rsImpl := roomserver.NewInternalAPI( base, keyRing, @@ -134,7 +134,7 @@ func main() { EDUInternalAPI: eduInputAPI, FederationSenderAPI: fsAPI, RoomserverAPI: rsAPI, - ServerKeyAPI: serverKeyAPI, + ServerKeyAPI: skAPI, UserAPI: userAPI, KeyAPI: keyAPI, } diff --git a/cmd/dendrite-room-server/main.go b/cmd/dendrite-room-server/main.go index 08ad34bf..c61368bf 100644 --- a/cmd/dendrite-room-server/main.go +++ b/cmd/dendrite-room-server/main.go @@ -24,7 +24,7 @@ func main() { base := setup.NewBaseDendrite(cfg, "RoomServerAPI", true) defer base.Close() // nolint: errcheck - serverKeyAPI := base.ServerKeyAPIClient() + serverKeyAPI := base.SigningKeyServerHTTPClient() keyRing := serverKeyAPI.KeyRing() fsAPI := base.FederationSenderHTTPClient() diff --git a/cmd/dendrite-server-key-api-server/main.go b/cmd/dendrite-server-key-api-server/main.go deleted file mode 100644 index 1ad4ede2..00000000 --- a/cmd/dendrite-server-key-api-server/main.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020 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 main - -import ( - "github.com/matrix-org/dendrite/internal/setup" - "github.com/matrix-org/dendrite/serverkeyapi" -) - -func main() { - cfg := setup.ParseFlags(false) - base := setup.NewBaseDendrite(cfg, "ServerKeyAPI", true) - defer base.Close() // nolint: errcheck - - federation := base.CreateFederationClient() - - intAPI := serverkeyapi.NewInternalAPI(&base.Cfg.ServerKeyAPI, federation, base.Caches) - serverkeyapi.AddInternalRoutes(base.InternalAPIMux, intAPI, base.Caches) - - base.SetupAndServeHTTP( - base.Cfg.ServerKeyAPI.InternalAPI.Listen, - setup.NoExternalListener, - nil, nil, - ) -} diff --git a/cmd/dendrite-signing-key-server/main.go b/cmd/dendrite-signing-key-server/main.go new file mode 100644 index 00000000..003bd755 --- /dev/null +++ b/cmd/dendrite-signing-key-server/main.go @@ -0,0 +1,37 @@ +// Copyright 2020 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 main + +import ( + "github.com/matrix-org/dendrite/internal/setup" + "github.com/matrix-org/dendrite/signingkeyserver" +) + +func main() { + cfg := setup.ParseFlags(false) + base := setup.NewBaseDendrite(cfg, "SigningKeyServer", true) + defer base.Close() // nolint: errcheck + + federation := base.CreateFederationClient() + + intAPI := signingkeyserver.NewInternalAPI(&base.Cfg.SigningKeyServer, federation, base.Caches) + signingkeyserver.AddInternalRoutes(base.InternalAPIMux, intAPI, base.Caches) + + base.SetupAndServeHTTP( + base.Cfg.SigningKeyServer.InternalAPI.Listen, + setup.NoExternalListener, + nil, nil, + ) +} diff --git a/cmd/dendritejs/main.go b/cmd/dendritejs/main.go index 12dc2d7c..267259c7 100644 --- a/cmd/dendritejs/main.go +++ b/cmd/dendritejs/main.go @@ -168,7 +168,7 @@ func main() { cfg.FederationSender.Database.ConnectionString = "file:/idb/dendritejs_fedsender.db" cfg.MediaAPI.Database.ConnectionString = "file:/idb/dendritejs_mediaapi.db" cfg.RoomServer.Database.ConnectionString = "file:/idb/dendritejs_roomserver.db" - cfg.ServerKeyAPI.Database.ConnectionString = "file:/idb/dendritejs_serverkey.db" + cfg.SigningKeyServer.Database.ConnectionString = "file:/idb/dendritejs_signingkeyserver.db" cfg.SyncAPI.Database.ConnectionString = "file:/idb/dendritejs_syncapi.db" cfg.KeyServer.Database.ConnectionString = "file:/idb/dendritejs_e2ekey.db" cfg.Global.Kafka.UseNaffka = true diff --git a/cmd/generate-config/main.go b/cmd/generate-config/main.go index 78ed3af6..e65723e6 100644 --- a/cmd/generate-config/main.go +++ b/cmd/generate-config/main.go @@ -27,7 +27,7 @@ func main() { }, }, } - cfg.ServerKeyAPI.KeyPerspectives = config.KeyPerspectives{ + cfg.SigningKeyServer.KeyPerspectives = config.KeyPerspectives{ { ServerName: "matrix.org", Keys: []config.KeyPerspectiveTrustKey{ diff --git a/dendrite-config.yaml b/dendrite-config.yaml index 74fa9b3e..6e87bc70 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -252,13 +252,13 @@ room_server: max_idle_conns: 2 conn_max_lifetime: -1 -# Configuration for the Server Key API (for server signing keys). -server_key_api: +# Configuration for the Signing Key Server (for server signing keys). +signing_key_server: internal_api: listen: http://localhost:7780 connect: http://localhost:7780 database: - connection_string: file:serverkeyapi.db + connection_string: file:signingkeyserver.db max_open_conns: 100 max_idle_conns: 2 conn_max_lifetime: -1 diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 7a7fb03e..913bc583 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -109,7 +109,7 @@ Assuming that Postgres 9.5 (or later) is installed: * Create the component databases: ```bash - for i in account device mediaapi syncapi roomserver serverkey federationsender appservice e2ekey naffka; do + for i in account device mediaapi syncapi roomserver signingkeyserver federationsender appservice e2ekey naffka; do sudo -u postgres createdb -O dendrite dendrite_$i done ``` diff --git a/internal/config/config.go b/internal/config/config.go index 74d3f4fa..9d9e2414 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -62,7 +62,7 @@ type Dendrite struct { KeyServer KeyServer `yaml:"key_server"` MediaAPI MediaAPI `yaml:"media_api"` RoomServer RoomServer `yaml:"room_server"` - ServerKeyAPI ServerKeyAPI `yaml:"server_key_api"` + SigningKeyServer SigningKeyServer `yaml:"signing_key_server"` SyncAPI SyncAPI `yaml:"sync_api"` UserAPI UserAPI `yaml:"user_api"` @@ -302,7 +302,7 @@ func (c *Dendrite) Defaults() { c.KeyServer.Defaults() c.MediaAPI.Defaults() c.RoomServer.Defaults() - c.ServerKeyAPI.Defaults() + c.SigningKeyServer.Defaults() c.SyncAPI.Defaults() c.UserAPI.Defaults() c.AppServiceAPI.Defaults() @@ -318,7 +318,7 @@ func (c *Dendrite) Verify(configErrs *ConfigErrors, isMonolith bool) { &c.Global, &c.ClientAPI, &c.EDUServer, &c.FederationAPI, &c.FederationSender, &c.KeyServer, &c.MediaAPI, &c.RoomServer, - &c.ServerKeyAPI, &c.SyncAPI, &c.UserAPI, + &c.SigningKeyServer, &c.SyncAPI, &c.UserAPI, &c.AppServiceAPI, } { c.Verify(configErrs, isMonolith) @@ -333,7 +333,7 @@ func (c *Dendrite) Wiring() { c.KeyServer.Matrix = &c.Global c.MediaAPI.Matrix = &c.Global c.RoomServer.Matrix = &c.Global - c.ServerKeyAPI.Matrix = &c.Global + c.SigningKeyServer.Matrix = &c.Global c.SyncAPI.Matrix = &c.Global c.UserAPI.Matrix = &c.Global c.AppServiceAPI.Matrix = &c.Global @@ -524,13 +524,13 @@ func (config *Dendrite) FederationSenderURL() string { return string(config.FederationSender.InternalAPI.Connect) } -// ServerKeyAPIURL returns an HTTP URL for where the server key API is listening. -func (config *Dendrite) ServerKeyAPIURL() string { - // Hard code the server key API server to talk HTTP for now. +// SigningKeyServerURL returns an HTTP URL for where the signing key server is listening. +func (config *Dendrite) SigningKeyServerURL() string { + // Hard code the signing key server to talk HTTP for now. // If we support HTTPS we need to think of a practical way to do certificate validation. // People setting up servers shouldn't need to get a certificate valid for the public // internet for an internal API. - return string(config.ServerKeyAPI.InternalAPI.Connect) + return string(config.SigningKeyServer.InternalAPI.Connect) } // KeyServerURL returns an HTTP URL for where the key server is listening. diff --git a/internal/config/config_serverkey.go b/internal/config/config_serverkey.go deleted file mode 100644 index 788a2fa0..00000000 --- a/internal/config/config_serverkey.go +++ /dev/null @@ -1,52 +0,0 @@ -package config - -import "github.com/matrix-org/gomatrixserverlib" - -type ServerKeyAPI struct { - Matrix *Global `yaml:"-"` - - InternalAPI InternalAPIOptions `yaml:"internal_api"` - - // The ServerKey database caches the public keys of remote servers. - // It may be accessed by the FederationAPI, the ClientAPI, and the MediaAPI. - Database DatabaseOptions `yaml:"database"` - - // Perspective keyservers, to use as a backup when direct key fetch - // requests don't succeed - KeyPerspectives KeyPerspectives `yaml:"key_perspectives"` - - // Should we prefer direct key fetches over perspective ones? - PreferDirectFetch bool `yaml:"prefer_direct_fetch"` -} - -func (c *ServerKeyAPI) Defaults() { - c.InternalAPI.Listen = "http://localhost:7780" - c.InternalAPI.Connect = "http://localhost:7780" - c.Database.Defaults() - c.Database.ConnectionString = "file:serverkeyapi.db" -} - -func (c *ServerKeyAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkURL(configErrs, "server_key_api.internal_api.listen", string(c.InternalAPI.Listen)) - checkURL(configErrs, "server_key_api.internal_api.bind", string(c.InternalAPI.Connect)) - checkNotEmpty(configErrs, "server_key_api.database.connection_string", string(c.Database.ConnectionString)) -} - -// KeyPerspectives are used to configure perspective key servers for -// retrieving server keys. -type KeyPerspectives []KeyPerspective - -type KeyPerspective struct { - // The server name of the perspective key server - ServerName gomatrixserverlib.ServerName `yaml:"server_name"` - // Server keys for the perspective user, used to verify the - // keys have been signed by the perspective server - Keys []KeyPerspectiveTrustKey `yaml:"keys"` -} - -type KeyPerspectiveTrustKey struct { - // The key ID, e.g. ed25519:auto - KeyID gomatrixserverlib.KeyID `yaml:"key_id"` - // The public key in base64 unpadded format - PublicKey string `yaml:"public_key"` -} diff --git a/internal/config/config_signingkeyserver.go b/internal/config/config_signingkeyserver.go new file mode 100644 index 00000000..51aca38b --- /dev/null +++ b/internal/config/config_signingkeyserver.go @@ -0,0 +1,52 @@ +package config + +import "github.com/matrix-org/gomatrixserverlib" + +type SigningKeyServer struct { + Matrix *Global `yaml:"-"` + + InternalAPI InternalAPIOptions `yaml:"internal_api"` + + // The SigningKeyServer database caches the public keys of remote servers. + // It may be accessed by the FederationAPI, the ClientAPI, and the MediaAPI. + Database DatabaseOptions `yaml:"database"` + + // Perspective keyservers, to use as a backup when direct key fetch + // requests don't succeed + KeyPerspectives KeyPerspectives `yaml:"key_perspectives"` + + // Should we prefer direct key fetches over perspective ones? + PreferDirectFetch bool `yaml:"prefer_direct_fetch"` +} + +func (c *SigningKeyServer) Defaults() { + c.InternalAPI.Listen = "http://localhost:7780" + c.InternalAPI.Connect = "http://localhost:7780" + c.Database.Defaults() + c.Database.ConnectionString = "file:signingkeyserver.db" +} + +func (c *SigningKeyServer) Verify(configErrs *ConfigErrors, isMonolith bool) { + checkURL(configErrs, "signing_key_server.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "signing_key_server.internal_api.bind", string(c.InternalAPI.Connect)) + checkNotEmpty(configErrs, "signing_key_server.database.connection_string", string(c.Database.ConnectionString)) +} + +// KeyPerspectives are used to configure perspective key servers for +// retrieving server keys. +type KeyPerspectives []KeyPerspective + +type KeyPerspective struct { + // The server name of the perspective key server + ServerName gomatrixserverlib.ServerName `yaml:"server_name"` + // Server keys for the perspective user, used to verify the + // keys have been signed by the perspective server + Keys []KeyPerspectiveTrustKey `yaml:"keys"` +} + +type KeyPerspectiveTrustKey struct { + // The key ID, e.g. ed25519:auto + KeyID gomatrixserverlib.KeyID `yaml:"key_id"` + // The public key in base64 unpadded format + PublicKey string `yaml:"public_key"` +} diff --git a/internal/setup/base.go b/internal/setup/base.go index f9ddfdf7..6a0a8bbd 100644 --- a/internal/setup/base.go +++ b/internal/setup/base.go @@ -46,8 +46,8 @@ import ( keyinthttp "github.com/matrix-org/dendrite/keyserver/inthttp" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" rsinthttp "github.com/matrix-org/dendrite/roomserver/inthttp" - serverKeyAPI "github.com/matrix-org/dendrite/serverkeyapi/api" - skinthttp "github.com/matrix-org/dendrite/serverkeyapi/inthttp" + skapi "github.com/matrix-org/dendrite/signingkeyserver/api" + skinthttp "github.com/matrix-org/dendrite/signingkeyserver/inthttp" userapi "github.com/matrix-org/dendrite/userapi/api" userapiinthttp "github.com/matrix-org/dendrite/userapi/inthttp" "github.com/sirupsen/logrus" @@ -208,15 +208,15 @@ func (b *BaseDendrite) FederationSenderHTTPClient() federationSenderAPI.Federati return f } -// ServerKeyAPIClient returns ServerKeyInternalAPI for hitting the server key API over HTTP -func (b *BaseDendrite) ServerKeyAPIClient() serverKeyAPI.ServerKeyInternalAPI { - f, err := skinthttp.NewServerKeyClient( - b.Cfg.ServerKeyAPIURL(), +// SigningKeyServerHTTPClient returns SigningKeyServer for hitting the signing key server over HTTP +func (b *BaseDendrite) SigningKeyServerHTTPClient() skapi.SigningKeyServerAPI { + f, err := skinthttp.NewSigningKeyServerClient( + b.Cfg.SigningKeyServerURL(), b.apiHttpClient, b.Caches, ) if err != nil { - logrus.WithError(err).Panic("NewServerKeyInternalAPIHTTP failed", b.httpClient) + logrus.WithError(err).Panic("SigningKeyServerHTTPClient failed", b.httpClient) } return f } diff --git a/internal/setup/monolith.go b/internal/setup/monolith.go index 2274283e..a0675d61 100644 --- a/internal/setup/monolith.go +++ b/internal/setup/monolith.go @@ -28,7 +28,7 @@ import ( keyAPI "github.com/matrix-org/dendrite/keyserver/api" "github.com/matrix-org/dendrite/mediaapi" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" - serverKeyAPI "github.com/matrix-org/dendrite/serverkeyapi/api" + serverKeyAPI "github.com/matrix-org/dendrite/signingkeyserver/api" "github.com/matrix-org/dendrite/syncapi" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/storage/accounts" @@ -50,7 +50,7 @@ type Monolith struct { EDUInternalAPI eduServerAPI.EDUServerInputAPI FederationSenderAPI federationSenderAPI.FederationSenderInternalAPI RoomserverAPI roomserverAPI.RoomserverInternalAPI - ServerKeyAPI serverKeyAPI.ServerKeyInternalAPI + ServerKeyAPI serverKeyAPI.SigningKeyServerAPI UserAPI userapi.UserInternalAPI KeyAPI keyAPI.KeyInternalAPI diff --git a/internal/test/config.go b/internal/test/config.go index 8080988f..69fc5a87 100644 --- a/internal/test/config.go +++ b/internal/test/config.go @@ -92,7 +92,7 @@ func MakeConfig(configDir, kafkaURI, database, host string, startPort int) (*con cfg.KeyServer.Database.ConnectionString = config.DataSource(database) cfg.MediaAPI.Database.ConnectionString = config.DataSource(database) cfg.RoomServer.Database.ConnectionString = config.DataSource(database) - cfg.ServerKeyAPI.Database.ConnectionString = config.DataSource(database) + cfg.SigningKeyServer.Database.ConnectionString = config.DataSource(database) cfg.SyncAPI.Database.ConnectionString = config.DataSource(database) cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(database) cfg.UserAPI.DeviceDatabase.ConnectionString = config.DataSource(database) @@ -104,7 +104,7 @@ func MakeConfig(configDir, kafkaURI, database, host string, startPort int) (*con cfg.KeyServer.InternalAPI.Listen = assignAddress() cfg.MediaAPI.InternalAPI.Listen = assignAddress() cfg.RoomServer.InternalAPI.Listen = assignAddress() - cfg.ServerKeyAPI.InternalAPI.Listen = assignAddress() + cfg.SigningKeyServer.InternalAPI.Listen = assignAddress() cfg.SyncAPI.InternalAPI.Listen = assignAddress() cfg.UserAPI.InternalAPI.Listen = assignAddress() @@ -115,7 +115,7 @@ func MakeConfig(configDir, kafkaURI, database, host string, startPort int) (*con cfg.KeyServer.InternalAPI.Connect = cfg.KeyServer.InternalAPI.Listen cfg.MediaAPI.InternalAPI.Connect = cfg.MediaAPI.InternalAPI.Listen cfg.RoomServer.InternalAPI.Connect = cfg.RoomServer.InternalAPI.Listen - cfg.ServerKeyAPI.InternalAPI.Connect = cfg.ServerKeyAPI.InternalAPI.Listen + cfg.SigningKeyServer.InternalAPI.Connect = cfg.SigningKeyServer.InternalAPI.Listen cfg.SyncAPI.InternalAPI.Connect = cfg.SyncAPI.InternalAPI.Listen cfg.UserAPI.InternalAPI.Connect = cfg.UserAPI.InternalAPI.Listen diff --git a/roomserver/roomserver.go b/roomserver/roomserver.go index 98a86e5b..4c138116 100644 --- a/roomserver/roomserver.go +++ b/roomserver/roomserver.go @@ -42,7 +42,7 @@ func NewInternalAPI( cfg := &base.Cfg.RoomServer var perspectiveServerNames []gomatrixserverlib.ServerName - for _, kp := range base.Cfg.ServerKeyAPI.KeyPerspectives { + for _, kp := range base.Cfg.SigningKeyServer.KeyPerspectives { perspectiveServerNames = append(perspectiveServerNames, kp.ServerName) } diff --git a/serverkeyapi/api/api.go b/serverkeyapi/api/api.go deleted file mode 100644 index 7af62634..00000000 --- a/serverkeyapi/api/api.go +++ /dev/null @@ -1,40 +0,0 @@ -package api - -import ( - "context" - - "github.com/matrix-org/gomatrixserverlib" -) - -type ServerKeyInternalAPI interface { - gomatrixserverlib.KeyDatabase - - KeyRing() *gomatrixserverlib.KeyRing - - InputPublicKeys( - ctx context.Context, - request *InputPublicKeysRequest, - response *InputPublicKeysResponse, - ) error - - QueryPublicKeys( - ctx context.Context, - request *QueryPublicKeysRequest, - response *QueryPublicKeysResponse, - ) error -} - -type QueryPublicKeysRequest struct { - Requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp `json:"requests"` -} - -type QueryPublicKeysResponse struct { - Results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult `json:"results"` -} - -type InputPublicKeysRequest struct { - Keys map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult `json:"keys"` -} - -type InputPublicKeysResponse struct { -} diff --git a/serverkeyapi/internal/api.go b/serverkeyapi/internal/api.go deleted file mode 100644 index 335bfe4c..00000000 --- a/serverkeyapi/internal/api.go +++ /dev/null @@ -1,270 +0,0 @@ -package internal - -import ( - "context" - "crypto/ed25519" - "fmt" - "time" - - "github.com/matrix-org/dendrite/internal/config" - "github.com/matrix-org/dendrite/serverkeyapi/api" - "github.com/matrix-org/gomatrixserverlib" - "github.com/sirupsen/logrus" -) - -type ServerKeyAPI struct { - api.ServerKeyInternalAPI - - ServerName gomatrixserverlib.ServerName - ServerPublicKey ed25519.PublicKey - ServerKeyID gomatrixserverlib.KeyID - ServerKeyValidity time.Duration - OldServerKeys []config.OldVerifyKeys - - OurKeyRing gomatrixserverlib.KeyRing - FedClient gomatrixserverlib.KeyClient -} - -func (s *ServerKeyAPI) KeyRing() *gomatrixserverlib.KeyRing { - // Return a keyring that forces requests to be proxied through the - // below functions. That way we can enforce things like validity - // and keeping the cache up-to-date. - return &gomatrixserverlib.KeyRing{ - KeyDatabase: s, - KeyFetchers: []gomatrixserverlib.KeyFetcher{}, - } -} - -func (s *ServerKeyAPI) StoreKeys( - _ context.Context, - results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, -) error { - // Run in a background context - we don't want to stop this work just - // because the caller gives up waiting. - ctx := context.Background() - - // Store any keys that we were given in our database. - return s.OurKeyRing.KeyDatabase.StoreKeys(ctx, results) -} - -func (s *ServerKeyAPI) FetchKeys( - _ context.Context, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, -) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { - // Run in a background context - we don't want to stop this work just - // because the caller gives up waiting. - ctx := context.Background() - now := gomatrixserverlib.AsTimestamp(time.Now()) - results := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{} - origRequests := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{} - for k, v := range requests { - origRequests[k] = v - } - - // First, check if any of these key checks are for our own keys. If - // they are then we will satisfy them directly. - s.handleLocalKeys(ctx, requests, results) - - // Then consult our local database and see if we have the requested - // keys. These might come from a cache, depending on the database - // implementation used. - if err := s.handleDatabaseKeys(ctx, now, requests, results); err != nil { - return nil, err - } - - // For any key requests that we still have outstanding, next try to - // fetch them directly. We'll go through each of the key fetchers to - // ask for the remaining keys - for _, fetcher := range s.OurKeyRing.KeyFetchers { - // If there are no more keys to look up then stop. - if len(requests) == 0 { - break - } - - // Ask the fetcher to look up our keys. - if err := s.handleFetcherKeys(ctx, now, fetcher, requests, results); err != nil { - logrus.WithError(err).WithFields(logrus.Fields{ - "fetcher_name": fetcher.FetcherName(), - }).Errorf("Failed to retrieve %d key(s)", len(requests)) - continue - } - } - - // Check that we've actually satisfied all of the key requests that we - // were given. We should report an error if we didn't. - for req := range origRequests { - if _, ok := results[req]; !ok { - // The results don't contain anything for this specific request, so - // we've failed to satisfy it from local keys, database keys or from - // all of the fetchers. Report an error. - logrus.Warnf("Failed to retrieve key %q for server %q", req.KeyID, req.ServerName) - } - } - - // Return the keys. - return results, nil -} - -func (s *ServerKeyAPI) FetcherName() string { - return fmt.Sprintf("ServerKeyAPI (wrapping %q)", s.OurKeyRing.KeyDatabase.FetcherName()) -} - -// handleLocalKeys handles cases where the key request contains -// a request for our own server keys, either current or old. -func (s *ServerKeyAPI) handleLocalKeys( - _ context.Context, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, - results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, -) { - for req := range requests { - if req.ServerName != s.ServerName { - continue - } - if req.KeyID == s.ServerKeyID { - // We found a key request that is supposed to be for our own - // keys. Remove it from the request list so we don't hit the - // database or the fetchers for it. - delete(requests, req) - - // Insert our own key into the response. - results[req] = gomatrixserverlib.PublicKeyLookupResult{ - VerifyKey: gomatrixserverlib.VerifyKey{ - Key: gomatrixserverlib.Base64Bytes(s.ServerPublicKey), - }, - ExpiredTS: gomatrixserverlib.PublicKeyNotExpired, - ValidUntilTS: gomatrixserverlib.AsTimestamp(time.Now().Add(s.ServerKeyValidity)), - } - } else { - // The key request doesn't match our current key. Let's see - // if it matches any of our old verify keys. - for _, oldVerifyKey := range s.OldServerKeys { - if req.KeyID == oldVerifyKey.KeyID { - // We found a key request that is supposed to be an expired - // key. - delete(requests, req) - - // Insert our own key into the response. - results[req] = gomatrixserverlib.PublicKeyLookupResult{ - VerifyKey: gomatrixserverlib.VerifyKey{ - Key: gomatrixserverlib.Base64Bytes(oldVerifyKey.PrivateKey.Public().(ed25519.PublicKey)), - }, - ExpiredTS: oldVerifyKey.ExpiredAt, - ValidUntilTS: gomatrixserverlib.PublicKeyNotValid, - } - - // No need to look at the other keys. - break - } - } - } - } -} - -// handleDatabaseKeys handles cases where the key requests can be -// satisfied from our local database/cache. -func (s *ServerKeyAPI) handleDatabaseKeys( - ctx context.Context, - now gomatrixserverlib.Timestamp, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, - results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, -) error { - // Ask the database/cache for the keys. - dbResults, err := s.OurKeyRing.KeyDatabase.FetchKeys(ctx, requests) - if err != nil { - return err - } - - // We successfully got some keys. Add them to the results. - for req, res := range dbResults { - // The key we've retrieved from the database/cache might - // have passed its validity period, but right now, it's - // the best thing we've got, and it might be sufficient to - // verify a past event. - results[req] = res - - // If the key is valid right now then we can also remove it - // from the request list as we don't need to fetch it again - // in that case. If the key isn't valid right now, then by - // leaving it in the 'requests' map, we'll try to update the - // key using the fetchers in handleFetcherKeys. - if res.WasValidAt(now, true) { - delete(requests, req) - } - } - return nil -} - -// handleFetcherKeys handles cases where a fetcher can satisfy -// the remaining requests. -func (s *ServerKeyAPI) handleFetcherKeys( - ctx context.Context, - _ gomatrixserverlib.Timestamp, - fetcher gomatrixserverlib.KeyFetcher, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, - results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, -) error { - logrus.WithFields(logrus.Fields{ - "fetcher_name": fetcher.FetcherName(), - }).Infof("Fetching %d key(s)", len(requests)) - - // Create a context that limits our requests to 30 seconds. - fetcherCtx, fetcherCancel := context.WithTimeout(ctx, time.Second*30) - defer fetcherCancel() - - // Try to fetch the keys. - fetcherResults, err := fetcher.FetchKeys(fetcherCtx, requests) - if err != nil { - return fmt.Errorf("fetcher.FetchKeys: %w", err) - } - - // Build a map of the results that we want to commit to the - // database. We do this in a separate map because otherwise we - // might end up trying to rewrite database entries. - storeResults := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{} - - // Now let's look at the results that we got from this fetcher. - for req, res := range fetcherResults { - if req.ServerName == s.ServerName { - continue - } - - if prev, ok := results[req]; ok { - // We've already got a previous entry for this request - // so let's see if the newly retrieved one contains a more - // up-to-date validity period. - if res.ValidUntilTS > prev.ValidUntilTS { - // This key is newer than the one we had so let's store - // it in the database. - storeResults[req] = res - } - } else { - // We didn't already have a previous entry for this request - // so store it in the database anyway for now. - storeResults[req] = res - } - - // Update the results map with this new result. If nothing - // else, we can try verifying against this key. - results[req] = res - - // Remove it from the request list so we won't re-fetch it. - delete(requests, req) - } - - // Store the keys from our store map. - if err = s.OurKeyRing.KeyDatabase.StoreKeys(context.Background(), storeResults); err != nil { - logrus.WithError(err).WithFields(logrus.Fields{ - "fetcher_name": fetcher.FetcherName(), - "database_name": s.OurKeyRing.KeyDatabase.FetcherName(), - }).Errorf("Failed to store keys in the database") - return fmt.Errorf("server key API failed to store retrieved keys: %w", err) - } - - if len(storeResults) > 0 { - logrus.WithFields(logrus.Fields{ - "fetcher_name": fetcher.FetcherName(), - }).Infof("Updated %d of %d key(s) in database (%d keys remaining)", len(storeResults), len(results), len(requests)) - } - - return nil -} diff --git a/serverkeyapi/inthttp/client.go b/serverkeyapi/inthttp/client.go deleted file mode 100644 index 39ab8c6c..00000000 --- a/serverkeyapi/inthttp/client.go +++ /dev/null @@ -1,132 +0,0 @@ -package inthttp - -import ( - "context" - "errors" - "net/http" - - "github.com/matrix-org/dendrite/internal/caching" - "github.com/matrix-org/dendrite/internal/httputil" - "github.com/matrix-org/dendrite/serverkeyapi/api" - "github.com/matrix-org/gomatrixserverlib" - "github.com/opentracing/opentracing-go" -) - -// HTTP paths for the internal HTTP APIs -const ( - ServerKeyInputPublicKeyPath = "/serverkeyapi/inputPublicKey" - ServerKeyQueryPublicKeyPath = "/serverkeyapi/queryPublicKey" -) - -// NewServerKeyClient creates a ServerKeyInternalAPI implemented by talking to a HTTP POST API. -// If httpClient is nil an error is returned -func NewServerKeyClient( - serverKeyAPIURL string, - httpClient *http.Client, - cache caching.ServerKeyCache, -) (api.ServerKeyInternalAPI, error) { - if httpClient == nil { - return nil, errors.New("NewRoomserverInternalAPIHTTP: httpClient is ") - } - return &httpServerKeyInternalAPI{ - serverKeyAPIURL: serverKeyAPIURL, - httpClient: httpClient, - cache: cache, - }, nil -} - -type httpServerKeyInternalAPI struct { - serverKeyAPIURL string - httpClient *http.Client - cache caching.ServerKeyCache -} - -func (s *httpServerKeyInternalAPI) KeyRing() *gomatrixserverlib.KeyRing { - // This is a bit of a cheat - we tell gomatrixserverlib that this API is - // both the key database and the key fetcher. While this does have the - // rather unfortunate effect of preventing gomatrixserverlib from handling - // key fetchers directly, we can at least reimplement this behaviour on - // the other end of the API. - return &gomatrixserverlib.KeyRing{ - KeyDatabase: s, - KeyFetchers: []gomatrixserverlib.KeyFetcher{}, - } -} - -func (s *httpServerKeyInternalAPI) FetcherName() string { - return "httpServerKeyInternalAPI" -} - -func (s *httpServerKeyInternalAPI) StoreKeys( - _ context.Context, - results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, -) error { - // Run in a background context - we don't want to stop this work just - // because the caller gives up waiting. - ctx := context.Background() - request := api.InputPublicKeysRequest{ - Keys: make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult), - } - response := api.InputPublicKeysResponse{} - for req, res := range results { - request.Keys[req] = res - s.cache.StoreServerKey(req, res) - } - return s.InputPublicKeys(ctx, &request, &response) -} - -func (s *httpServerKeyInternalAPI) FetchKeys( - _ context.Context, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, -) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { - // Run in a background context - we don't want to stop this work just - // because the caller gives up waiting. - ctx := context.Background() - result := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) - request := api.QueryPublicKeysRequest{ - Requests: make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp), - } - response := api.QueryPublicKeysResponse{ - Results: make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult), - } - for req, ts := range requests { - if res, ok := s.cache.GetServerKey(req, ts); ok { - result[req] = res - continue - } - request.Requests[req] = ts - } - err := s.QueryPublicKeys(ctx, &request, &response) - if err != nil { - return nil, err - } - for req, res := range response.Results { - result[req] = res - s.cache.StoreServerKey(req, res) - } - return result, nil -} - -func (h *httpServerKeyInternalAPI) InputPublicKeys( - ctx context.Context, - request *api.InputPublicKeysRequest, - response *api.InputPublicKeysResponse, -) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "InputPublicKey") - defer span.Finish() - - apiURL := h.serverKeyAPIURL + ServerKeyInputPublicKeyPath - return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) -} - -func (h *httpServerKeyInternalAPI) QueryPublicKeys( - ctx context.Context, - request *api.QueryPublicKeysRequest, - response *api.QueryPublicKeysResponse, -) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "QueryPublicKey") - defer span.Finish() - - apiURL := h.serverKeyAPIURL + ServerKeyQueryPublicKeyPath - return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) -} diff --git a/serverkeyapi/inthttp/server.go b/serverkeyapi/inthttp/server.go deleted file mode 100644 index cd474839..00000000 --- a/serverkeyapi/inthttp/server.go +++ /dev/null @@ -1,43 +0,0 @@ -package inthttp - -import ( - "encoding/json" - "net/http" - - "github.com/gorilla/mux" - "github.com/matrix-org/dendrite/internal/caching" - "github.com/matrix-org/dendrite/internal/httputil" - "github.com/matrix-org/dendrite/serverkeyapi/api" - "github.com/matrix-org/util" -) - -func AddRoutes(s api.ServerKeyInternalAPI, internalAPIMux *mux.Router, cache caching.ServerKeyCache) { - internalAPIMux.Handle(ServerKeyQueryPublicKeyPath, - httputil.MakeInternalAPI("queryPublicKeys", func(req *http.Request) util.JSONResponse { - request := api.QueryPublicKeysRequest{} - response := api.QueryPublicKeysResponse{} - if err := json.NewDecoder(req.Body).Decode(&request); err != nil { - return util.MessageResponse(http.StatusBadRequest, err.Error()) - } - keys, err := s.FetchKeys(req.Context(), request.Requests) - if err != nil { - return util.ErrorResponse(err) - } - response.Results = keys - return util.JSONResponse{Code: http.StatusOK, JSON: &response} - }), - ) - internalAPIMux.Handle(ServerKeyInputPublicKeyPath, - httputil.MakeInternalAPI("inputPublicKeys", func(req *http.Request) util.JSONResponse { - request := api.InputPublicKeysRequest{} - response := api.InputPublicKeysResponse{} - if err := json.NewDecoder(req.Body).Decode(&request); err != nil { - return util.MessageResponse(http.StatusBadRequest, err.Error()) - } - if err := s.StoreKeys(req.Context(), request.Keys); err != nil { - return util.ErrorResponse(err) - } - return util.JSONResponse{Code: http.StatusOK, JSON: &response} - }), - ) -} diff --git a/serverkeyapi/serverkeyapi.go b/serverkeyapi/serverkeyapi.go deleted file mode 100644 index da239eb0..00000000 --- a/serverkeyapi/serverkeyapi.go +++ /dev/null @@ -1,107 +0,0 @@ -package serverkeyapi - -import ( - "crypto/ed25519" - "encoding/base64" - - "github.com/gorilla/mux" - "github.com/matrix-org/dendrite/internal/caching" - "github.com/matrix-org/dendrite/internal/config" - "github.com/matrix-org/dendrite/serverkeyapi/api" - "github.com/matrix-org/dendrite/serverkeyapi/internal" - "github.com/matrix-org/dendrite/serverkeyapi/inthttp" - "github.com/matrix-org/dendrite/serverkeyapi/storage" - "github.com/matrix-org/dendrite/serverkeyapi/storage/cache" - "github.com/matrix-org/gomatrixserverlib" - "github.com/sirupsen/logrus" -) - -// AddInternalRoutes registers HTTP handlers for the internal API. Invokes functions -// on the given input API. -func AddInternalRoutes(router *mux.Router, intAPI api.ServerKeyInternalAPI, caches *caching.Caches) { - inthttp.AddRoutes(intAPI, router, caches) -} - -// NewInternalAPI returns a concerete implementation of the internal API. Callers -// can call functions directly on the returned API or via an HTTP interface using AddInternalRoutes. -func NewInternalAPI( - cfg *config.ServerKeyAPI, - fedClient gomatrixserverlib.KeyClient, - caches *caching.Caches, -) api.ServerKeyInternalAPI { - innerDB, err := storage.NewDatabase( - &cfg.Database, - cfg.Matrix.ServerName, - cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey), - cfg.Matrix.KeyID, - ) - if err != nil { - logrus.WithError(err).Panicf("failed to connect to server key database") - } - - serverKeyDB, err := cache.NewKeyDatabase(innerDB, caches) - if err != nil { - logrus.WithError(err).Panicf("failed to set up caching wrapper for server key database") - } - - internalAPI := internal.ServerKeyAPI{ - ServerName: cfg.Matrix.ServerName, - ServerPublicKey: cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey), - ServerKeyID: cfg.Matrix.KeyID, - ServerKeyValidity: cfg.Matrix.KeyValidityPeriod, - OldServerKeys: cfg.Matrix.OldVerifyKeys, - FedClient: fedClient, - OurKeyRing: gomatrixserverlib.KeyRing{ - KeyFetchers: []gomatrixserverlib.KeyFetcher{}, - KeyDatabase: serverKeyDB, - }, - } - - addDirectFetcher := func() { - internalAPI.OurKeyRing.KeyFetchers = append( - internalAPI.OurKeyRing.KeyFetchers, - &gomatrixserverlib.DirectKeyFetcher{ - Client: fedClient, - }, - ) - } - - if cfg.PreferDirectFetch { - addDirectFetcher() - } else { - defer addDirectFetcher() - } - - var b64e = base64.StdEncoding.WithPadding(base64.NoPadding) - for _, ps := range cfg.KeyPerspectives { - perspective := &gomatrixserverlib.PerspectiveKeyFetcher{ - PerspectiveServerName: ps.ServerName, - PerspectiveServerKeys: map[gomatrixserverlib.KeyID]ed25519.PublicKey{}, - Client: fedClient, - } - - for _, key := range ps.Keys { - rawkey, err := b64e.DecodeString(key.PublicKey) - if err != nil { - logrus.WithError(err).WithFields(logrus.Fields{ - "server_name": ps.ServerName, - "public_key": key.PublicKey, - }).Warn("Couldn't parse perspective key") - continue - } - perspective.PerspectiveServerKeys[key.KeyID] = rawkey - } - - internalAPI.OurKeyRing.KeyFetchers = append( - internalAPI.OurKeyRing.KeyFetchers, - perspective, - ) - - logrus.WithFields(logrus.Fields{ - "server_name": ps.ServerName, - "num_public_keys": len(ps.Keys), - }).Info("Enabled perspective key fetcher") - } - - return &internalAPI -} diff --git a/serverkeyapi/serverkeyapi_test.go b/serverkeyapi/serverkeyapi_test.go deleted file mode 100644 index 152a853e..00000000 --- a/serverkeyapi/serverkeyapi_test.go +++ /dev/null @@ -1,318 +0,0 @@ -package serverkeyapi - -import ( - "bytes" - "context" - "crypto/ed25519" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" - "reflect" - "testing" - "time" - - "github.com/matrix-org/dendrite/federationapi/routing" - "github.com/matrix-org/dendrite/internal/caching" - "github.com/matrix-org/dendrite/internal/config" - "github.com/matrix-org/dendrite/serverkeyapi/api" - "github.com/matrix-org/gomatrixserverlib" -) - -type server struct { - name gomatrixserverlib.ServerName // server name - validity time.Duration // key validity duration from now - config *config.ServerKeyAPI // skeleton config, from TestMain - fedconfig *config.FederationAPI // - fedclient *gomatrixserverlib.FederationClient // uses MockRoundTripper - cache *caching.Caches // server-specific cache - api api.ServerKeyInternalAPI // server-specific server key API -} - -func (s *server) renew() { - // This updates the validity period to be an hour in the - // future, which is particularly useful in server A and - // server C's cases which have validity either as now or - // in the past. - s.validity = time.Hour - s.config.Matrix.KeyValidityPeriod = s.validity -} - -var ( - serverKeyID = gomatrixserverlib.KeyID("ed25519:auto") - serverA = &server{name: "a.com", validity: time.Duration(0)} // expires now - serverB = &server{name: "b.com", validity: time.Hour} // expires in an hour - serverC = &server{name: "c.com", validity: -time.Hour} // expired an hour ago -) - -var servers = map[string]*server{ - "a.com": serverA, - "b.com": serverB, - "c.com": serverC, -} - -func TestMain(m *testing.M) { - // Set up the server key API for each "server" that we - // will use in our tests. - for _, s := range servers { - // Generate a new key. - _, testPriv, err := ed25519.GenerateKey(nil) - if err != nil { - panic("can't generate identity key: " + err.Error()) - } - - // Create a new cache but don't enable prometheus! - s.cache, err = caching.NewInMemoryLRUCache(false) - if err != nil { - panic("can't create cache: " + err.Error()) - } - - // Draw up just enough Dendrite config for the server key - // API to work. - cfg := &config.Dendrite{} - cfg.Defaults() - cfg.Global.ServerName = gomatrixserverlib.ServerName(s.name) - cfg.Global.PrivateKey = testPriv - cfg.Global.KeyID = serverKeyID - cfg.Global.KeyValidityPeriod = s.validity - cfg.ServerKeyAPI.Database.ConnectionString = config.DataSource("file::memory:") - s.config = &cfg.ServerKeyAPI - s.fedconfig = &cfg.FederationAPI - - // Create a transport which redirects federation requests to - // the mock round tripper. Since we're not *really* listening for - // federation requests then this will return the key instead. - transport := &http.Transport{} - transport.RegisterProtocol("matrix", &MockRoundTripper{}) - - // Create the federation client. - s.fedclient = gomatrixserverlib.NewFederationClientWithTransport( - s.config.Matrix.ServerName, serverKeyID, testPriv, true, transport, - ) - - // Finally, build the server key APIs. - s.api = NewInternalAPI(s.config, s.fedclient, s.cache) - } - - // Now that we have built our server key APIs, start the - // rest of the tests. - os.Exit(m.Run()) -} - -type MockRoundTripper struct{} - -func (m *MockRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) { - // Check if the request is looking for keys from a server that - // we know about in the test. The only reason this should go wrong - // is if the test is broken. - s, ok := servers[req.Host] - if !ok { - return nil, fmt.Errorf("server not known: %s", req.Host) - } - - // We're intercepting /matrix/key/v2/server requests here, so check - // that the URL supplied in the request is for that. - if req.URL.Path != "/_matrix/key/v2/server" { - return nil, fmt.Errorf("unexpected request path: %s", req.URL.Path) - } - - // Get the keys and JSON-ify them. - keys := routing.LocalKeys(s.fedconfig) - body, err := json.MarshalIndent(keys.JSON, "", " ") - if err != nil { - return nil, err - } - - // And respond. - res = &http.Response{ - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader(body)), - } - return -} - -func TestServersRequestOwnKeys(t *testing.T) { - // Each server will request its own keys. There's no reason - // for this to fail as each server should know its own keys. - - for name, s := range servers { - req := gomatrixserverlib.PublicKeyLookupRequest{ - ServerName: s.name, - KeyID: serverKeyID, - } - res, err := s.api.FetchKeys( - context.Background(), - map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{ - req: gomatrixserverlib.AsTimestamp(time.Now()), - }, - ) - if err != nil { - t.Fatalf("server could not fetch own key: %s", err) - } - if _, ok := res[req]; !ok { - t.Fatalf("server didn't return its own key in the results") - } - t.Logf("%s's key expires at %s\n", name, res[req].ValidUntilTS.Time()) - } -} - -func TestCachingBehaviour(t *testing.T) { - // Server A will request Server B's key, which has a validity - // period of an hour from now. We should retrieve the key and - // it should make it into the cache automatically. - - req := gomatrixserverlib.PublicKeyLookupRequest{ - ServerName: serverB.name, - KeyID: serverKeyID, - } - ts := gomatrixserverlib.AsTimestamp(time.Now()) - - res, err := serverA.api.FetchKeys( - context.Background(), - map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{ - req: ts, - }, - ) - if err != nil { - t.Fatalf("server A failed to retrieve server B key: %s", err) - } - if len(res) != 1 { - t.Fatalf("server B should have returned one key but instead returned %d keys", len(res)) - } - if _, ok := res[req]; !ok { - t.Fatalf("server B isn't included in the key fetch response") - } - - // At this point, if the previous key request was a success, - // then the cache should now contain the key. Check if that's - // the case - if it isn't then there's something wrong with - // the cache implementation or we failed to get the key. - - cres, ok := serverA.cache.GetServerKey(req, ts) - if !ok { - t.Fatalf("server B key should be in cache but isn't") - } - if !reflect.DeepEqual(cres, res[req]) { - t.Fatalf("the cached result from server B wasn't what server B gave us") - } - - // If we ask the cache for the same key but this time for an event - // that happened in +30 minutes. Since the validity period is for - // another hour, then we should get a response back from the cache. - - _, ok = serverA.cache.GetServerKey( - req, - gomatrixserverlib.AsTimestamp(time.Now().Add(time.Minute*30)), - ) - if !ok { - t.Fatalf("server B key isn't in cache when it should be (+30 minutes)") - } - - // If we ask the cache for the same key but this time for an event - // that happened in +90 minutes then we should expect to get no - // cache result. This is because the cache shouldn't return a result - // that is obviously past the validity of the event. - - _, ok = serverA.cache.GetServerKey( - req, - gomatrixserverlib.AsTimestamp(time.Now().Add(time.Minute*90)), - ) - if ok { - t.Fatalf("server B key is in cache when it shouldn't be (+90 minutes)") - } -} - -func TestRenewalBehaviour(t *testing.T) { - // Server A will request Server C's key but their validity period - // is an hour in the past. We'll retrieve the key as, even though it's - // past its validity, it will be able to verify past events. - - req := gomatrixserverlib.PublicKeyLookupRequest{ - ServerName: serverC.name, - KeyID: serverKeyID, - } - - res, err := serverA.api.FetchKeys( - context.Background(), - map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{ - req: gomatrixserverlib.AsTimestamp(time.Now()), - }, - ) - if err != nil { - t.Fatalf("server A failed to retrieve server C key: %s", err) - } - if len(res) != 1 { - t.Fatalf("server C should have returned one key but instead returned %d keys", len(res)) - } - if _, ok := res[req]; !ok { - t.Fatalf("server C isn't included in the key fetch response") - } - - // If we ask the cache for the server key for an event that happened - // 90 minutes ago then we should get a cache result, as the key hadn't - // passed its validity by that point. The fact that the key is now in - // the cache is, in itself, proof that we successfully retrieved the - // key before. - - oldcached, ok := serverA.cache.GetServerKey( - req, - gomatrixserverlib.AsTimestamp(time.Now().Add(-time.Minute*90)), - ) - if !ok { - t.Fatalf("server C key isn't in cache when it should be (-90 minutes)") - } - - // If we now ask the cache for the same key but this time for an event - // that only happened 30 minutes ago then we shouldn't get a cached - // result, as the event happened after the key validity expired. This - // is really just for sanity checking. - - _, ok = serverA.cache.GetServerKey( - req, - gomatrixserverlib.AsTimestamp(time.Now().Add(-time.Minute*30)), - ) - if ok { - t.Fatalf("server B key is in cache when it shouldn't be (-30 minutes)") - } - - // We're now going to kick server C into renewing its key. Since we're - // happy at this point that the key that we already have is from the past - // then repeating a key fetch should cause us to try and renew the key. - // If so, then the new key will end up in our cache. - - serverC.renew() - - res, err = serverA.api.FetchKeys( - context.Background(), - map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{ - req: gomatrixserverlib.AsTimestamp(time.Now()), - }, - ) - if err != nil { - t.Fatalf("server A failed to retrieve server C key: %s", err) - } - if len(res) != 1 { - t.Fatalf("server C should have returned one key but instead returned %d keys", len(res)) - } - if _, ok = res[req]; !ok { - t.Fatalf("server C isn't included in the key fetch response") - } - - // We're now going to ask the cache what the new key validity is. If - // it is still the same as the previous validity then we've failed to - // retrieve the renewed key. If it's newer then we've successfully got - // the renewed key. - - newcached, ok := serverA.cache.GetServerKey( - req, - gomatrixserverlib.AsTimestamp(time.Now().Add(-time.Minute*30)), - ) - if !ok { - t.Fatalf("server B key isn't in cache when it shouldn't be (post-renewal)") - } - if oldcached.ValidUntilTS >= newcached.ValidUntilTS { - t.Fatalf("the server B key should have been renewed but wasn't") - } - t.Log(res) -} diff --git a/serverkeyapi/storage/cache/keydb.go b/serverkeyapi/storage/cache/keydb.go deleted file mode 100644 index 2063dfc5..00000000 --- a/serverkeyapi/storage/cache/keydb.go +++ /dev/null @@ -1,68 +0,0 @@ -package cache - -import ( - "context" - "errors" - - "github.com/matrix-org/dendrite/internal/caching" - "github.com/matrix-org/gomatrixserverlib" -) - -// A Database implements gomatrixserverlib.KeyDatabase and is used to store -// the public keys for other matrix servers. -type KeyDatabase struct { - inner gomatrixserverlib.KeyDatabase - cache caching.ServerKeyCache -} - -func NewKeyDatabase(inner gomatrixserverlib.KeyDatabase, cache caching.ServerKeyCache) (*KeyDatabase, error) { - if inner == nil { - return nil, errors.New("inner database can't be nil") - } - if cache == nil { - return nil, errors.New("cache can't be nil") - } - return &KeyDatabase{ - inner: inner, - cache: cache, - }, nil -} - -// FetcherName implements KeyFetcher -func (d KeyDatabase) FetcherName() string { - return "InMemoryKeyCache" -} - -// FetchKeys implements gomatrixserverlib.KeyDatabase -func (d *KeyDatabase) FetchKeys( - ctx context.Context, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, -) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { - results := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) - for req, ts := range requests { - if res, cached := d.cache.GetServerKey(req, ts); cached { - results[req] = res - delete(requests, req) - } - } - fromDB, err := d.inner.FetchKeys(ctx, requests) - if err != nil { - return results, err - } - for req, res := range fromDB { - results[req] = res - d.cache.StoreServerKey(req, res) - } - return results, nil -} - -// StoreKeys implements gomatrixserverlib.KeyDatabase -func (d *KeyDatabase) StoreKeys( - ctx context.Context, - keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, -) error { - for req, res := range keyMap { - d.cache.StoreServerKey(req, res) - } - return d.inner.StoreKeys(ctx, keyMap) -} diff --git a/serverkeyapi/storage/interface.go b/serverkeyapi/storage/interface.go deleted file mode 100644 index 3a67ac55..00000000 --- a/serverkeyapi/storage/interface.go +++ /dev/null @@ -1,13 +0,0 @@ -package storage - -import ( - "context" - - "github.com/matrix-org/gomatrixserverlib" -) - -type Database interface { - FetcherName() string - FetchKeys(ctx context.Context, requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) - StoreKeys(ctx context.Context, keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) error -} diff --git a/serverkeyapi/storage/keydb.go b/serverkeyapi/storage/keydb.go deleted file mode 100644 index 3d3a0c30..00000000 --- a/serverkeyapi/storage/keydb.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020 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. - -// +build !wasm - -package storage - -import ( - "fmt" - - "golang.org/x/crypto/ed25519" - - "github.com/matrix-org/dendrite/internal/config" - "github.com/matrix-org/dendrite/serverkeyapi/storage/postgres" - "github.com/matrix-org/dendrite/serverkeyapi/storage/sqlite3" - "github.com/matrix-org/gomatrixserverlib" -) - -// NewDatabase opens a database connection. -func NewDatabase( - dbProperties *config.DatabaseOptions, - serverName gomatrixserverlib.ServerName, - serverKey ed25519.PublicKey, - serverKeyID gomatrixserverlib.KeyID, -) (Database, error) { - switch { - case dbProperties.ConnectionString.IsSQLite(): - return sqlite3.NewDatabase(dbProperties, serverName, serverKey, serverKeyID) - case dbProperties.ConnectionString.IsPostgres(): - return postgres.NewDatabase(dbProperties, serverName, serverKey, serverKeyID) - default: - return nil, fmt.Errorf("unexpected database type") - } -} diff --git a/serverkeyapi/storage/keydb_wasm.go b/serverkeyapi/storage/keydb_wasm.go deleted file mode 100644 index de66a1d6..00000000 --- a/serverkeyapi/storage/keydb_wasm.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2020 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. - -// +build wasm - -package storage - -import ( - "fmt" - "net/url" - - "golang.org/x/crypto/ed25519" - - "github.com/matrix-org/dendrite/internal/sqlutil" - "github.com/matrix-org/dendrite/serverkeyapi/storage/sqlite3" - "github.com/matrix-org/gomatrixserverlib" -) - -// NewDatabase opens a database connection. -func NewDatabase( - dataSourceName string, - dbProperties sqlutil.DbProperties, // nolint:unparam - serverName gomatrixserverlib.ServerName, - serverKey ed25519.PublicKey, - serverKeyID gomatrixserverlib.KeyID, -) (Database, error) { - uri, err := url.Parse(dataSourceName) - if err != nil { - return nil, err - } - switch uri.Scheme { - case "postgres": - return nil, fmt.Errorf("Cannot use postgres implementation") - case "file": - return sqlite3.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID) - default: - return nil, fmt.Errorf("Cannot use postgres implementation") - } -} diff --git a/serverkeyapi/storage/postgres/keydb.go b/serverkeyapi/storage/postgres/keydb.go deleted file mode 100644 index 63444085..00000000 --- a/serverkeyapi/storage/postgres/keydb.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2017-2018 New Vector Ltd -// Copyright 2019-2020 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 postgres - -import ( - "context" - - "golang.org/x/crypto/ed25519" - - "github.com/matrix-org/dendrite/internal/config" - "github.com/matrix-org/dendrite/internal/sqlutil" - "github.com/matrix-org/gomatrixserverlib" -) - -// A Database implements gomatrixserverlib.KeyDatabase and is used to store -// the public keys for other matrix servers. -type Database struct { - statements serverKeyStatements -} - -// NewDatabase prepares a new key database. -// It creates the necessary tables if they don't already exist. -// It prepares all the SQL statements that it will use. -// Returns an error if there was a problem talking to the database. -func NewDatabase( - dbProperties *config.DatabaseOptions, - serverName gomatrixserverlib.ServerName, - serverKey ed25519.PublicKey, - serverKeyID gomatrixserverlib.KeyID, -) (*Database, error) { - db, err := sqlutil.Open(dbProperties) - if err != nil { - return nil, err - } - d := &Database{} - err = d.statements.prepare(db) - if err != nil { - return nil, err - } - return d, nil -} - -// FetcherName implements KeyFetcher -func (d Database) FetcherName() string { - return "PostgresKeyDatabase" -} - -// FetchKeys implements gomatrixserverlib.KeyDatabase -func (d *Database) FetchKeys( - ctx context.Context, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, -) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { - return d.statements.bulkSelectServerKeys(ctx, requests) -} - -// StoreKeys implements gomatrixserverlib.KeyDatabase -func (d *Database) StoreKeys( - ctx context.Context, - keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, -) error { - // TODO: Inserting all the keys within a single transaction may - // be more efficient since the transaction overhead can be quite - // high for a single insert statement. - var lastErr error - for request, keys := range keyMap { - if err := d.statements.upsertServerKeys(ctx, request, keys); err != nil { - // Rather than returning immediately on error we try to insert the - // remaining keys. - // Since we are inserting the keys outside of a transaction it is - // possible for some of the inserts to succeed even though some - // of the inserts have failed. - // Ensuring that we always insert all the keys we can means that - // this behaviour won't depend on the iteration order of the map. - lastErr = err - } - } - return lastErr -} diff --git a/serverkeyapi/storage/postgres/server_key_table.go b/serverkeyapi/storage/postgres/server_key_table.go deleted file mode 100644 index 87f1c211..00000000 --- a/serverkeyapi/storage/postgres/server_key_table.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2017-2018 New Vector Ltd -// Copyright 2019-2020 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 postgres - -import ( - "context" - "database/sql" - - "github.com/lib/pq" - "github.com/matrix-org/dendrite/internal" - "github.com/matrix-org/gomatrixserverlib" -) - -const serverKeysSchema = ` --- A cache of signing keys downloaded from remote servers. -CREATE TABLE IF NOT EXISTS keydb_server_keys ( - -- The name of the matrix server the key is for. - server_name TEXT NOT NULL, - -- The ID of the server key. - server_key_id TEXT NOT NULL, - -- Combined server name and key ID separated by the ASCII unit separator - -- to make it easier to run bulk queries. - server_name_and_key_id TEXT NOT NULL, - -- When the key is valid until as a millisecond timestamp. - -- 0 if this is an expired key (in which case expired_ts will be non-zero) - valid_until_ts BIGINT NOT NULL, - -- When the key expired as a millisecond timestamp. - -- 0 if this is an active key (in which case valid_until_ts will be non-zero) - expired_ts BIGINT NOT NULL, - -- The base64-encoded public key. - server_key TEXT NOT NULL, - CONSTRAINT keydb_server_keys_unique UNIQUE (server_name, server_key_id) -); - -CREATE INDEX IF NOT EXISTS keydb_server_name_and_key_id ON keydb_server_keys (server_name_and_key_id); -` - -const bulkSelectServerKeysSQL = "" + - "SELECT server_name, server_key_id, valid_until_ts, expired_ts, " + - " server_key FROM keydb_server_keys" + - " WHERE server_name_and_key_id = ANY($1)" - -const upsertServerKeysSQL = "" + - "INSERT INTO keydb_server_keys (server_name, server_key_id," + - " server_name_and_key_id, valid_until_ts, expired_ts, server_key)" + - " VALUES ($1, $2, $3, $4, $5, $6)" + - " ON CONFLICT ON CONSTRAINT keydb_server_keys_unique" + - " DO UPDATE SET valid_until_ts = $4, expired_ts = $5, server_key = $6" - -type serverKeyStatements struct { - bulkSelectServerKeysStmt *sql.Stmt - upsertServerKeysStmt *sql.Stmt -} - -func (s *serverKeyStatements) prepare(db *sql.DB) (err error) { - _, err = db.Exec(serverKeysSchema) - if err != nil { - return - } - if s.bulkSelectServerKeysStmt, err = db.Prepare(bulkSelectServerKeysSQL); err != nil { - return - } - if s.upsertServerKeysStmt, err = db.Prepare(upsertServerKeysSQL); err != nil { - return - } - return -} - -func (s *serverKeyStatements) bulkSelectServerKeys( - ctx context.Context, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, -) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { - var nameAndKeyIDs []string - for request := range requests { - nameAndKeyIDs = append(nameAndKeyIDs, nameAndKeyID(request)) - } - stmt := s.bulkSelectServerKeysStmt - rows, err := stmt.QueryContext(ctx, pq.StringArray(nameAndKeyIDs)) - if err != nil { - return nil, err - } - defer internal.CloseAndLogIfError(ctx, rows, "bulkSelectServerKeys: rows.close() failed") - results := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{} - for rows.Next() { - var serverName string - var keyID string - var key string - var validUntilTS int64 - var expiredTS int64 - if err = rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil { - return nil, err - } - r := gomatrixserverlib.PublicKeyLookupRequest{ - ServerName: gomatrixserverlib.ServerName(serverName), - KeyID: gomatrixserverlib.KeyID(keyID), - } - vk := gomatrixserverlib.VerifyKey{} - err = vk.Key.Decode(key) - if err != nil { - return nil, err - } - results[r] = gomatrixserverlib.PublicKeyLookupResult{ - VerifyKey: vk, - ValidUntilTS: gomatrixserverlib.Timestamp(validUntilTS), - ExpiredTS: gomatrixserverlib.Timestamp(expiredTS), - } - } - return results, rows.Err() -} - -func (s *serverKeyStatements) upsertServerKeys( - ctx context.Context, - request gomatrixserverlib.PublicKeyLookupRequest, - key gomatrixserverlib.PublicKeyLookupResult, -) error { - _, err := s.upsertServerKeysStmt.ExecContext( - ctx, - string(request.ServerName), - string(request.KeyID), - nameAndKeyID(request), - key.ValidUntilTS, - key.ExpiredTS, - key.Key.Encode(), - ) - return err -} - -func nameAndKeyID(request gomatrixserverlib.PublicKeyLookupRequest) string { - return string(request.ServerName) + "\x1F" + string(request.KeyID) -} diff --git a/serverkeyapi/storage/sqlite3/keydb.go b/serverkeyapi/storage/sqlite3/keydb.go deleted file mode 100644 index 0ee74bc1..00000000 --- a/serverkeyapi/storage/sqlite3/keydb.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2017-2018 New Vector Ltd -// Copyright 2019-2020 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 sqlite3 - -import ( - "context" - - "golang.org/x/crypto/ed25519" - - "github.com/matrix-org/dendrite/internal/config" - "github.com/matrix-org/dendrite/internal/sqlutil" - "github.com/matrix-org/gomatrixserverlib" - - _ "github.com/mattn/go-sqlite3" -) - -// A Database implements gomatrixserverlib.KeyDatabase and is used to store -// the public keys for other matrix servers. -type Database struct { - writer sqlutil.Writer - statements serverKeyStatements -} - -// NewDatabase prepares a new key database. -// It creates the necessary tables if they don't already exist. -// It prepares all the SQL statements that it will use. -// Returns an error if there was a problem talking to the database. -func NewDatabase( - dbProperties *config.DatabaseOptions, - serverName gomatrixserverlib.ServerName, - serverKey ed25519.PublicKey, - serverKeyID gomatrixserverlib.KeyID, -) (*Database, error) { - db, err := sqlutil.Open(dbProperties) - if err != nil { - return nil, err - } - d := &Database{ - writer: sqlutil.NewExclusiveWriter(), - } - err = d.statements.prepare(db, d.writer) - if err != nil { - return nil, err - } - if err != nil { - return nil, err - } - return d, nil -} - -// FetcherName implements KeyFetcher -func (d Database) FetcherName() string { - return "SqliteKeyDatabase" -} - -// FetchKeys implements gomatrixserverlib.KeyDatabase -func (d *Database) FetchKeys( - ctx context.Context, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, -) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { - return d.statements.bulkSelectServerKeys(ctx, requests) -} - -// StoreKeys implements gomatrixserverlib.KeyDatabase -func (d *Database) StoreKeys( - ctx context.Context, - keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, -) error { - // TODO: Inserting all the keys within a single transaction may - // be more efficient since the transaction overhead can be quite - // high for a single insert statement. - var lastErr error - for request, keys := range keyMap { - if err := d.statements.upsertServerKeys(ctx, request, keys); err != nil { - // Rather than returning immediately on error we try to insert the - // remaining keys. - // Since we are inserting the keys outside of a transaction it is - // possible for some of the inserts to succeed even though some - // of the inserts have failed. - // Ensuring that we always insert all the keys we can means that - // this behaviour won't depend on the iteration order of the map. - lastErr = err - } - } - return lastErr -} diff --git a/serverkeyapi/storage/sqlite3/server_key_table.go b/serverkeyapi/storage/sqlite3/server_key_table.go deleted file mode 100644 index 2484d636..00000000 --- a/serverkeyapi/storage/sqlite3/server_key_table.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2017-2018 New Vector Ltd -// Copyright 2019-2020 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 sqlite3 - -import ( - "context" - "database/sql" - "fmt" - - "github.com/matrix-org/dendrite/internal/sqlutil" - "github.com/matrix-org/gomatrixserverlib" -) - -const serverKeysSchema = ` --- A cache of signing keys downloaded from remote servers. -CREATE TABLE IF NOT EXISTS keydb_server_keys ( - -- The name of the matrix server the key is for. - server_name TEXT NOT NULL, - -- The ID of the server key. - server_key_id TEXT NOT NULL, - -- Combined server name and key ID separated by the ASCII unit separator - -- to make it easier to run bulk queries. - server_name_and_key_id TEXT NOT NULL, - -- When the key is valid until as a millisecond timestamp. - -- 0 if this is an expired key (in which case expired_ts will be non-zero) - valid_until_ts BIGINT NOT NULL, - -- When the key expired as a millisecond timestamp. - -- 0 if this is an active key (in which case valid_until_ts will be non-zero) - expired_ts BIGINT NOT NULL, - -- The base64-encoded public key. - server_key TEXT NOT NULL, - UNIQUE (server_name, server_key_id) -); - -CREATE INDEX IF NOT EXISTS keydb_server_name_and_key_id ON keydb_server_keys (server_name_and_key_id); -` - -const bulkSelectServerKeysSQL = "" + - "SELECT server_name, server_key_id, valid_until_ts, expired_ts, " + - " server_key FROM keydb_server_keys" + - " WHERE server_name_and_key_id IN ($1)" - -const upsertServerKeysSQL = "" + - "INSERT INTO keydb_server_keys (server_name, server_key_id," + - " server_name_and_key_id, valid_until_ts, expired_ts, server_key)" + - " VALUES ($1, $2, $3, $4, $5, $6)" + - " ON CONFLICT (server_name, server_key_id)" + - " DO UPDATE SET valid_until_ts = $4, expired_ts = $5, server_key = $6" - -type serverKeyStatements struct { - db *sql.DB - writer sqlutil.Writer - bulkSelectServerKeysStmt *sql.Stmt - upsertServerKeysStmt *sql.Stmt -} - -func (s *serverKeyStatements) prepare(db *sql.DB, writer sqlutil.Writer) (err error) { - s.db = db - s.writer = writer - _, err = db.Exec(serverKeysSchema) - if err != nil { - return - } - if s.bulkSelectServerKeysStmt, err = db.Prepare(bulkSelectServerKeysSQL); err != nil { - return - } - if s.upsertServerKeysStmt, err = db.Prepare(upsertServerKeysSQL); err != nil { - return - } - return -} - -func (s *serverKeyStatements) bulkSelectServerKeys( - ctx context.Context, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, -) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { - nameAndKeyIDs := make([]string, 0, len(requests)) - for request := range requests { - nameAndKeyIDs = append(nameAndKeyIDs, nameAndKeyID(request)) - } - results := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, len(requests)) - iKeyIDs := make([]interface{}, len(nameAndKeyIDs)) - for i, v := range nameAndKeyIDs { - iKeyIDs[i] = v - } - - err := sqlutil.RunLimitedVariablesQuery( - ctx, bulkSelectServerKeysSQL, s.db, iKeyIDs, sqlutil.SQLite3MaxVariables, - func(rows *sql.Rows) error { - for rows.Next() { - var serverName string - var keyID string - var key string - var validUntilTS int64 - var expiredTS int64 - if err := rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil { - return fmt.Errorf("bulkSelectServerKeys: %v", err) - } - r := gomatrixserverlib.PublicKeyLookupRequest{ - ServerName: gomatrixserverlib.ServerName(serverName), - KeyID: gomatrixserverlib.KeyID(keyID), - } - vk := gomatrixserverlib.VerifyKey{} - err := vk.Key.Decode(key) - if err != nil { - return fmt.Errorf("bulkSelectServerKeys: %v", err) - } - results[r] = gomatrixserverlib.PublicKeyLookupResult{ - VerifyKey: vk, - ValidUntilTS: gomatrixserverlib.Timestamp(validUntilTS), - ExpiredTS: gomatrixserverlib.Timestamp(expiredTS), - } - } - return nil - }, - ) - - if err != nil { - return nil, err - } - return results, nil -} - -func (s *serverKeyStatements) upsertServerKeys( - ctx context.Context, - request gomatrixserverlib.PublicKeyLookupRequest, - key gomatrixserverlib.PublicKeyLookupResult, -) error { - return s.writer.Do(s.db, nil, func(txn *sql.Tx) error { - stmt := sqlutil.TxStmt(txn, s.upsertServerKeysStmt) - _, err := stmt.ExecContext( - ctx, - string(request.ServerName), - string(request.KeyID), - nameAndKeyID(request), - key.ValidUntilTS, - key.ExpiredTS, - key.Key.Encode(), - ) - return err - }) -} - -func nameAndKeyID(request gomatrixserverlib.PublicKeyLookupRequest) string { - return string(request.ServerName) + "\x1F" + string(request.KeyID) -} diff --git a/signingkeyserver/api/api.go b/signingkeyserver/api/api.go new file mode 100644 index 00000000..f053d72e --- /dev/null +++ b/signingkeyserver/api/api.go @@ -0,0 +1,40 @@ +package api + +import ( + "context" + + "github.com/matrix-org/gomatrixserverlib" +) + +type SigningKeyServerAPI interface { + gomatrixserverlib.KeyDatabase + + KeyRing() *gomatrixserverlib.KeyRing + + InputPublicKeys( + ctx context.Context, + request *InputPublicKeysRequest, + response *InputPublicKeysResponse, + ) error + + QueryPublicKeys( + ctx context.Context, + request *QueryPublicKeysRequest, + response *QueryPublicKeysResponse, + ) error +} + +type QueryPublicKeysRequest struct { + Requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp `json:"requests"` +} + +type QueryPublicKeysResponse struct { + Results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult `json:"results"` +} + +type InputPublicKeysRequest struct { + Keys map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult `json:"keys"` +} + +type InputPublicKeysResponse struct { +} diff --git a/signingkeyserver/internal/api.go b/signingkeyserver/internal/api.go new file mode 100644 index 00000000..54c41b52 --- /dev/null +++ b/signingkeyserver/internal/api.go @@ -0,0 +1,270 @@ +package internal + +import ( + "context" + "crypto/ed25519" + "fmt" + "time" + + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/signingkeyserver/api" + "github.com/matrix-org/gomatrixserverlib" + "github.com/sirupsen/logrus" +) + +type ServerKeyAPI struct { + api.SigningKeyServerAPI + + ServerName gomatrixserverlib.ServerName + ServerPublicKey ed25519.PublicKey + ServerKeyID gomatrixserverlib.KeyID + ServerKeyValidity time.Duration + OldServerKeys []config.OldVerifyKeys + + OurKeyRing gomatrixserverlib.KeyRing + FedClient gomatrixserverlib.KeyClient +} + +func (s *ServerKeyAPI) KeyRing() *gomatrixserverlib.KeyRing { + // Return a keyring that forces requests to be proxied through the + // below functions. That way we can enforce things like validity + // and keeping the cache up-to-date. + return &gomatrixserverlib.KeyRing{ + KeyDatabase: s, + KeyFetchers: []gomatrixserverlib.KeyFetcher{}, + } +} + +func (s *ServerKeyAPI) StoreKeys( + _ context.Context, + results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, +) error { + // Run in a background context - we don't want to stop this work just + // because the caller gives up waiting. + ctx := context.Background() + + // Store any keys that we were given in our database. + return s.OurKeyRing.KeyDatabase.StoreKeys(ctx, results) +} + +func (s *ServerKeyAPI) FetchKeys( + _ context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { + // Run in a background context - we don't want to stop this work just + // because the caller gives up waiting. + ctx := context.Background() + now := gomatrixserverlib.AsTimestamp(time.Now()) + results := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{} + origRequests := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{} + for k, v := range requests { + origRequests[k] = v + } + + // First, check if any of these key checks are for our own keys. If + // they are then we will satisfy them directly. + s.handleLocalKeys(ctx, requests, results) + + // Then consult our local database and see if we have the requested + // keys. These might come from a cache, depending on the database + // implementation used. + if err := s.handleDatabaseKeys(ctx, now, requests, results); err != nil { + return nil, err + } + + // For any key requests that we still have outstanding, next try to + // fetch them directly. We'll go through each of the key fetchers to + // ask for the remaining keys + for _, fetcher := range s.OurKeyRing.KeyFetchers { + // If there are no more keys to look up then stop. + if len(requests) == 0 { + break + } + + // Ask the fetcher to look up our keys. + if err := s.handleFetcherKeys(ctx, now, fetcher, requests, results); err != nil { + logrus.WithError(err).WithFields(logrus.Fields{ + "fetcher_name": fetcher.FetcherName(), + }).Errorf("Failed to retrieve %d key(s)", len(requests)) + continue + } + } + + // Check that we've actually satisfied all of the key requests that we + // were given. We should report an error if we didn't. + for req := range origRequests { + if _, ok := results[req]; !ok { + // The results don't contain anything for this specific request, so + // we've failed to satisfy it from local keys, database keys or from + // all of the fetchers. Report an error. + logrus.Warnf("Failed to retrieve key %q for server %q", req.KeyID, req.ServerName) + } + } + + // Return the keys. + return results, nil +} + +func (s *ServerKeyAPI) FetcherName() string { + return fmt.Sprintf("ServerKeyAPI (wrapping %q)", s.OurKeyRing.KeyDatabase.FetcherName()) +} + +// handleLocalKeys handles cases where the key request contains +// a request for our own server keys, either current or old. +func (s *ServerKeyAPI) handleLocalKeys( + _ context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, + results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, +) { + for req := range requests { + if req.ServerName != s.ServerName { + continue + } + if req.KeyID == s.ServerKeyID { + // We found a key request that is supposed to be for our own + // keys. Remove it from the request list so we don't hit the + // database or the fetchers for it. + delete(requests, req) + + // Insert our own key into the response. + results[req] = gomatrixserverlib.PublicKeyLookupResult{ + VerifyKey: gomatrixserverlib.VerifyKey{ + Key: gomatrixserverlib.Base64Bytes(s.ServerPublicKey), + }, + ExpiredTS: gomatrixserverlib.PublicKeyNotExpired, + ValidUntilTS: gomatrixserverlib.AsTimestamp(time.Now().Add(s.ServerKeyValidity)), + } + } else { + // The key request doesn't match our current key. Let's see + // if it matches any of our old verify keys. + for _, oldVerifyKey := range s.OldServerKeys { + if req.KeyID == oldVerifyKey.KeyID { + // We found a key request that is supposed to be an expired + // key. + delete(requests, req) + + // Insert our own key into the response. + results[req] = gomatrixserverlib.PublicKeyLookupResult{ + VerifyKey: gomatrixserverlib.VerifyKey{ + Key: gomatrixserverlib.Base64Bytes(oldVerifyKey.PrivateKey.Public().(ed25519.PublicKey)), + }, + ExpiredTS: oldVerifyKey.ExpiredAt, + ValidUntilTS: gomatrixserverlib.PublicKeyNotValid, + } + + // No need to look at the other keys. + break + } + } + } + } +} + +// handleDatabaseKeys handles cases where the key requests can be +// satisfied from our local database/cache. +func (s *ServerKeyAPI) handleDatabaseKeys( + ctx context.Context, + now gomatrixserverlib.Timestamp, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, + results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, +) error { + // Ask the database/cache for the keys. + dbResults, err := s.OurKeyRing.KeyDatabase.FetchKeys(ctx, requests) + if err != nil { + return err + } + + // We successfully got some keys. Add them to the results. + for req, res := range dbResults { + // The key we've retrieved from the database/cache might + // have passed its validity period, but right now, it's + // the best thing we've got, and it might be sufficient to + // verify a past event. + results[req] = res + + // If the key is valid right now then we can also remove it + // from the request list as we don't need to fetch it again + // in that case. If the key isn't valid right now, then by + // leaving it in the 'requests' map, we'll try to update the + // key using the fetchers in handleFetcherKeys. + if res.WasValidAt(now, true) { + delete(requests, req) + } + } + return nil +} + +// handleFetcherKeys handles cases where a fetcher can satisfy +// the remaining requests. +func (s *ServerKeyAPI) handleFetcherKeys( + ctx context.Context, + _ gomatrixserverlib.Timestamp, + fetcher gomatrixserverlib.KeyFetcher, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, + results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, +) error { + logrus.WithFields(logrus.Fields{ + "fetcher_name": fetcher.FetcherName(), + }).Infof("Fetching %d key(s)", len(requests)) + + // Create a context that limits our requests to 30 seconds. + fetcherCtx, fetcherCancel := context.WithTimeout(ctx, time.Second*30) + defer fetcherCancel() + + // Try to fetch the keys. + fetcherResults, err := fetcher.FetchKeys(fetcherCtx, requests) + if err != nil { + return fmt.Errorf("fetcher.FetchKeys: %w", err) + } + + // Build a map of the results that we want to commit to the + // database. We do this in a separate map because otherwise we + // might end up trying to rewrite database entries. + storeResults := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{} + + // Now let's look at the results that we got from this fetcher. + for req, res := range fetcherResults { + if req.ServerName == s.ServerName { + continue + } + + if prev, ok := results[req]; ok { + // We've already got a previous entry for this request + // so let's see if the newly retrieved one contains a more + // up-to-date validity period. + if res.ValidUntilTS > prev.ValidUntilTS { + // This key is newer than the one we had so let's store + // it in the database. + storeResults[req] = res + } + } else { + // We didn't already have a previous entry for this request + // so store it in the database anyway for now. + storeResults[req] = res + } + + // Update the results map with this new result. If nothing + // else, we can try verifying against this key. + results[req] = res + + // Remove it from the request list so we won't re-fetch it. + delete(requests, req) + } + + // Store the keys from our store map. + if err = s.OurKeyRing.KeyDatabase.StoreKeys(context.Background(), storeResults); err != nil { + logrus.WithError(err).WithFields(logrus.Fields{ + "fetcher_name": fetcher.FetcherName(), + "database_name": s.OurKeyRing.KeyDatabase.FetcherName(), + }).Errorf("Failed to store keys in the database") + return fmt.Errorf("server key API failed to store retrieved keys: %w", err) + } + + if len(storeResults) > 0 { + logrus.WithFields(logrus.Fields{ + "fetcher_name": fetcher.FetcherName(), + }).Infof("Updated %d of %d key(s) in database (%d keys remaining)", len(storeResults), len(results), len(requests)) + } + + return nil +} diff --git a/signingkeyserver/inthttp/client.go b/signingkeyserver/inthttp/client.go new file mode 100644 index 00000000..71e40b8f --- /dev/null +++ b/signingkeyserver/inthttp/client.go @@ -0,0 +1,132 @@ +package inthttp + +import ( + "context" + "errors" + "net/http" + + "github.com/matrix-org/dendrite/internal/caching" + "github.com/matrix-org/dendrite/internal/httputil" + "github.com/matrix-org/dendrite/signingkeyserver/api" + "github.com/matrix-org/gomatrixserverlib" + "github.com/opentracing/opentracing-go" +) + +// HTTP paths for the internal HTTP APIs +const ( + ServerKeyInputPublicKeyPath = "/signingkeyserver/inputPublicKey" + ServerKeyQueryPublicKeyPath = "/signingkeyserver/queryPublicKey" +) + +// NewSigningKeyServerClient creates a SigningKeyServerAPI implemented by talking to a HTTP POST API. +// If httpClient is nil an error is returned +func NewSigningKeyServerClient( + serverKeyAPIURL string, + httpClient *http.Client, + cache caching.ServerKeyCache, +) (api.SigningKeyServerAPI, error) { + if httpClient == nil { + return nil, errors.New("NewSigningKeyServerClient: httpClient is ") + } + return &httpServerKeyInternalAPI{ + serverKeyAPIURL: serverKeyAPIURL, + httpClient: httpClient, + cache: cache, + }, nil +} + +type httpServerKeyInternalAPI struct { + serverKeyAPIURL string + httpClient *http.Client + cache caching.ServerKeyCache +} + +func (s *httpServerKeyInternalAPI) KeyRing() *gomatrixserverlib.KeyRing { + // This is a bit of a cheat - we tell gomatrixserverlib that this API is + // both the key database and the key fetcher. While this does have the + // rather unfortunate effect of preventing gomatrixserverlib from handling + // key fetchers directly, we can at least reimplement this behaviour on + // the other end of the API. + return &gomatrixserverlib.KeyRing{ + KeyDatabase: s, + KeyFetchers: []gomatrixserverlib.KeyFetcher{}, + } +} + +func (s *httpServerKeyInternalAPI) FetcherName() string { + return "httpServerKeyInternalAPI" +} + +func (s *httpServerKeyInternalAPI) StoreKeys( + _ context.Context, + results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, +) error { + // Run in a background context - we don't want to stop this work just + // because the caller gives up waiting. + ctx := context.Background() + request := api.InputPublicKeysRequest{ + Keys: make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult), + } + response := api.InputPublicKeysResponse{} + for req, res := range results { + request.Keys[req] = res + s.cache.StoreServerKey(req, res) + } + return s.InputPublicKeys(ctx, &request, &response) +} + +func (s *httpServerKeyInternalAPI) FetchKeys( + _ context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { + // Run in a background context - we don't want to stop this work just + // because the caller gives up waiting. + ctx := context.Background() + result := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) + request := api.QueryPublicKeysRequest{ + Requests: make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp), + } + response := api.QueryPublicKeysResponse{ + Results: make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult), + } + for req, ts := range requests { + if res, ok := s.cache.GetServerKey(req, ts); ok { + result[req] = res + continue + } + request.Requests[req] = ts + } + err := s.QueryPublicKeys(ctx, &request, &response) + if err != nil { + return nil, err + } + for req, res := range response.Results { + result[req] = res + s.cache.StoreServerKey(req, res) + } + return result, nil +} + +func (h *httpServerKeyInternalAPI) InputPublicKeys( + ctx context.Context, + request *api.InputPublicKeysRequest, + response *api.InputPublicKeysResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "InputPublicKey") + defer span.Finish() + + apiURL := h.serverKeyAPIURL + ServerKeyInputPublicKeyPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} + +func (h *httpServerKeyInternalAPI) QueryPublicKeys( + ctx context.Context, + request *api.QueryPublicKeysRequest, + response *api.QueryPublicKeysResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryPublicKey") + defer span.Finish() + + apiURL := h.serverKeyAPIURL + ServerKeyQueryPublicKeyPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} diff --git a/signingkeyserver/inthttp/server.go b/signingkeyserver/inthttp/server.go new file mode 100644 index 00000000..d26f7380 --- /dev/null +++ b/signingkeyserver/inthttp/server.go @@ -0,0 +1,43 @@ +package inthttp + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/matrix-org/dendrite/internal/caching" + "github.com/matrix-org/dendrite/internal/httputil" + "github.com/matrix-org/dendrite/signingkeyserver/api" + "github.com/matrix-org/util" +) + +func AddRoutes(s api.SigningKeyServerAPI, internalAPIMux *mux.Router, cache caching.ServerKeyCache) { + internalAPIMux.Handle(ServerKeyQueryPublicKeyPath, + httputil.MakeInternalAPI("queryPublicKeys", func(req *http.Request) util.JSONResponse { + request := api.QueryPublicKeysRequest{} + response := api.QueryPublicKeysResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + keys, err := s.FetchKeys(req.Context(), request.Requests) + if err != nil { + return util.ErrorResponse(err) + } + response.Results = keys + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) + internalAPIMux.Handle(ServerKeyInputPublicKeyPath, + httputil.MakeInternalAPI("inputPublicKeys", func(req *http.Request) util.JSONResponse { + request := api.InputPublicKeysRequest{} + response := api.InputPublicKeysResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + if err := s.StoreKeys(req.Context(), request.Keys); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) +} diff --git a/signingkeyserver/serverkeyapi_test.go b/signingkeyserver/serverkeyapi_test.go new file mode 100644 index 00000000..e5578f43 --- /dev/null +++ b/signingkeyserver/serverkeyapi_test.go @@ -0,0 +1,318 @@ +package signingkeyserver + +import ( + "bytes" + "context" + "crypto/ed25519" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "reflect" + "testing" + "time" + + "github.com/matrix-org/dendrite/federationapi/routing" + "github.com/matrix-org/dendrite/internal/caching" + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/signingkeyserver/api" + "github.com/matrix-org/gomatrixserverlib" +) + +type server struct { + name gomatrixserverlib.ServerName // server name + validity time.Duration // key validity duration from now + config *config.SigningKeyServer // skeleton config, from TestMain + fedconfig *config.FederationAPI // + fedclient *gomatrixserverlib.FederationClient // uses MockRoundTripper + cache *caching.Caches // server-specific cache + api api.SigningKeyServerAPI // server-specific server key API +} + +func (s *server) renew() { + // This updates the validity period to be an hour in the + // future, which is particularly useful in server A and + // server C's cases which have validity either as now or + // in the past. + s.validity = time.Hour + s.config.Matrix.KeyValidityPeriod = s.validity +} + +var ( + serverKeyID = gomatrixserverlib.KeyID("ed25519:auto") + serverA = &server{name: "a.com", validity: time.Duration(0)} // expires now + serverB = &server{name: "b.com", validity: time.Hour} // expires in an hour + serverC = &server{name: "c.com", validity: -time.Hour} // expired an hour ago +) + +var servers = map[string]*server{ + "a.com": serverA, + "b.com": serverB, + "c.com": serverC, +} + +func TestMain(m *testing.M) { + // Set up the server key API for each "server" that we + // will use in our tests. + for _, s := range servers { + // Generate a new key. + _, testPriv, err := ed25519.GenerateKey(nil) + if err != nil { + panic("can't generate identity key: " + err.Error()) + } + + // Create a new cache but don't enable prometheus! + s.cache, err = caching.NewInMemoryLRUCache(false) + if err != nil { + panic("can't create cache: " + err.Error()) + } + + // Draw up just enough Dendrite config for the server key + // API to work. + cfg := &config.Dendrite{} + cfg.Defaults() + cfg.Global.ServerName = gomatrixserverlib.ServerName(s.name) + cfg.Global.PrivateKey = testPriv + cfg.Global.KeyID = serverKeyID + cfg.Global.KeyValidityPeriod = s.validity + cfg.SigningKeyServer.Database.ConnectionString = config.DataSource("file::memory:") + s.config = &cfg.SigningKeyServer + s.fedconfig = &cfg.FederationAPI + + // Create a transport which redirects federation requests to + // the mock round tripper. Since we're not *really* listening for + // federation requests then this will return the key instead. + transport := &http.Transport{} + transport.RegisterProtocol("matrix", &MockRoundTripper{}) + + // Create the federation client. + s.fedclient = gomatrixserverlib.NewFederationClientWithTransport( + s.config.Matrix.ServerName, serverKeyID, testPriv, true, transport, + ) + + // Finally, build the server key APIs. + s.api = NewInternalAPI(s.config, s.fedclient, s.cache) + } + + // Now that we have built our server key APIs, start the + // rest of the tests. + os.Exit(m.Run()) +} + +type MockRoundTripper struct{} + +func (m *MockRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) { + // Check if the request is looking for keys from a server that + // we know about in the test. The only reason this should go wrong + // is if the test is broken. + s, ok := servers[req.Host] + if !ok { + return nil, fmt.Errorf("server not known: %s", req.Host) + } + + // We're intercepting /matrix/key/v2/server requests here, so check + // that the URL supplied in the request is for that. + if req.URL.Path != "/_matrix/key/v2/server" { + return nil, fmt.Errorf("unexpected request path: %s", req.URL.Path) + } + + // Get the keys and JSON-ify them. + keys := routing.LocalKeys(s.fedconfig) + body, err := json.MarshalIndent(keys.JSON, "", " ") + if err != nil { + return nil, err + } + + // And respond. + res = &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewReader(body)), + } + return +} + +func TestServersRequestOwnKeys(t *testing.T) { + // Each server will request its own keys. There's no reason + // for this to fail as each server should know its own keys. + + for name, s := range servers { + req := gomatrixserverlib.PublicKeyLookupRequest{ + ServerName: s.name, + KeyID: serverKeyID, + } + res, err := s.api.FetchKeys( + context.Background(), + map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{ + req: gomatrixserverlib.AsTimestamp(time.Now()), + }, + ) + if err != nil { + t.Fatalf("server could not fetch own key: %s", err) + } + if _, ok := res[req]; !ok { + t.Fatalf("server didn't return its own key in the results") + } + t.Logf("%s's key expires at %s\n", name, res[req].ValidUntilTS.Time()) + } +} + +func TestCachingBehaviour(t *testing.T) { + // Server A will request Server B's key, which has a validity + // period of an hour from now. We should retrieve the key and + // it should make it into the cache automatically. + + req := gomatrixserverlib.PublicKeyLookupRequest{ + ServerName: serverB.name, + KeyID: serverKeyID, + } + ts := gomatrixserverlib.AsTimestamp(time.Now()) + + res, err := serverA.api.FetchKeys( + context.Background(), + map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{ + req: ts, + }, + ) + if err != nil { + t.Fatalf("server A failed to retrieve server B key: %s", err) + } + if len(res) != 1 { + t.Fatalf("server B should have returned one key but instead returned %d keys", len(res)) + } + if _, ok := res[req]; !ok { + t.Fatalf("server B isn't included in the key fetch response") + } + + // At this point, if the previous key request was a success, + // then the cache should now contain the key. Check if that's + // the case - if it isn't then there's something wrong with + // the cache implementation or we failed to get the key. + + cres, ok := serverA.cache.GetServerKey(req, ts) + if !ok { + t.Fatalf("server B key should be in cache but isn't") + } + if !reflect.DeepEqual(cres, res[req]) { + t.Fatalf("the cached result from server B wasn't what server B gave us") + } + + // If we ask the cache for the same key but this time for an event + // that happened in +30 minutes. Since the validity period is for + // another hour, then we should get a response back from the cache. + + _, ok = serverA.cache.GetServerKey( + req, + gomatrixserverlib.AsTimestamp(time.Now().Add(time.Minute*30)), + ) + if !ok { + t.Fatalf("server B key isn't in cache when it should be (+30 minutes)") + } + + // If we ask the cache for the same key but this time for an event + // that happened in +90 minutes then we should expect to get no + // cache result. This is because the cache shouldn't return a result + // that is obviously past the validity of the event. + + _, ok = serverA.cache.GetServerKey( + req, + gomatrixserverlib.AsTimestamp(time.Now().Add(time.Minute*90)), + ) + if ok { + t.Fatalf("server B key is in cache when it shouldn't be (+90 minutes)") + } +} + +func TestRenewalBehaviour(t *testing.T) { + // Server A will request Server C's key but their validity period + // is an hour in the past. We'll retrieve the key as, even though it's + // past its validity, it will be able to verify past events. + + req := gomatrixserverlib.PublicKeyLookupRequest{ + ServerName: serverC.name, + KeyID: serverKeyID, + } + + res, err := serverA.api.FetchKeys( + context.Background(), + map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{ + req: gomatrixserverlib.AsTimestamp(time.Now()), + }, + ) + if err != nil { + t.Fatalf("server A failed to retrieve server C key: %s", err) + } + if len(res) != 1 { + t.Fatalf("server C should have returned one key but instead returned %d keys", len(res)) + } + if _, ok := res[req]; !ok { + t.Fatalf("server C isn't included in the key fetch response") + } + + // If we ask the cache for the server key for an event that happened + // 90 minutes ago then we should get a cache result, as the key hadn't + // passed its validity by that point. The fact that the key is now in + // the cache is, in itself, proof that we successfully retrieved the + // key before. + + oldcached, ok := serverA.cache.GetServerKey( + req, + gomatrixserverlib.AsTimestamp(time.Now().Add(-time.Minute*90)), + ) + if !ok { + t.Fatalf("server C key isn't in cache when it should be (-90 minutes)") + } + + // If we now ask the cache for the same key but this time for an event + // that only happened 30 minutes ago then we shouldn't get a cached + // result, as the event happened after the key validity expired. This + // is really just for sanity checking. + + _, ok = serverA.cache.GetServerKey( + req, + gomatrixserverlib.AsTimestamp(time.Now().Add(-time.Minute*30)), + ) + if ok { + t.Fatalf("server B key is in cache when it shouldn't be (-30 minutes)") + } + + // We're now going to kick server C into renewing its key. Since we're + // happy at this point that the key that we already have is from the past + // then repeating a key fetch should cause us to try and renew the key. + // If so, then the new key will end up in our cache. + + serverC.renew() + + res, err = serverA.api.FetchKeys( + context.Background(), + map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{ + req: gomatrixserverlib.AsTimestamp(time.Now()), + }, + ) + if err != nil { + t.Fatalf("server A failed to retrieve server C key: %s", err) + } + if len(res) != 1 { + t.Fatalf("server C should have returned one key but instead returned %d keys", len(res)) + } + if _, ok = res[req]; !ok { + t.Fatalf("server C isn't included in the key fetch response") + } + + // We're now going to ask the cache what the new key validity is. If + // it is still the same as the previous validity then we've failed to + // retrieve the renewed key. If it's newer then we've successfully got + // the renewed key. + + newcached, ok := serverA.cache.GetServerKey( + req, + gomatrixserverlib.AsTimestamp(time.Now().Add(-time.Minute*30)), + ) + if !ok { + t.Fatalf("server B key isn't in cache when it shouldn't be (post-renewal)") + } + if oldcached.ValidUntilTS >= newcached.ValidUntilTS { + t.Fatalf("the server B key should have been renewed but wasn't") + } + t.Log(res) +} diff --git a/signingkeyserver/signingkeyserver.go b/signingkeyserver/signingkeyserver.go new file mode 100644 index 00000000..27b4c703 --- /dev/null +++ b/signingkeyserver/signingkeyserver.go @@ -0,0 +1,107 @@ +package signingkeyserver + +import ( + "crypto/ed25519" + "encoding/base64" + + "github.com/gorilla/mux" + "github.com/matrix-org/dendrite/internal/caching" + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/signingkeyserver/api" + "github.com/matrix-org/dendrite/signingkeyserver/internal" + "github.com/matrix-org/dendrite/signingkeyserver/inthttp" + "github.com/matrix-org/dendrite/signingkeyserver/storage" + "github.com/matrix-org/dendrite/signingkeyserver/storage/cache" + "github.com/matrix-org/gomatrixserverlib" + "github.com/sirupsen/logrus" +) + +// AddInternalRoutes registers HTTP handlers for the internal API. Invokes functions +// on the given input API. +func AddInternalRoutes(router *mux.Router, intAPI api.SigningKeyServerAPI, caches *caching.Caches) { + inthttp.AddRoutes(intAPI, router, caches) +} + +// NewInternalAPI returns a concerete implementation of the internal API. Callers +// can call functions directly on the returned API or via an HTTP interface using AddInternalRoutes. +func NewInternalAPI( + cfg *config.SigningKeyServer, + fedClient gomatrixserverlib.KeyClient, + caches *caching.Caches, +) api.SigningKeyServerAPI { + innerDB, err := storage.NewDatabase( + &cfg.Database, + cfg.Matrix.ServerName, + cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey), + cfg.Matrix.KeyID, + ) + if err != nil { + logrus.WithError(err).Panicf("failed to connect to server key database") + } + + serverKeyDB, err := cache.NewKeyDatabase(innerDB, caches) + if err != nil { + logrus.WithError(err).Panicf("failed to set up caching wrapper for server key database") + } + + internalAPI := internal.ServerKeyAPI{ + ServerName: cfg.Matrix.ServerName, + ServerPublicKey: cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey), + ServerKeyID: cfg.Matrix.KeyID, + ServerKeyValidity: cfg.Matrix.KeyValidityPeriod, + OldServerKeys: cfg.Matrix.OldVerifyKeys, + FedClient: fedClient, + OurKeyRing: gomatrixserverlib.KeyRing{ + KeyFetchers: []gomatrixserverlib.KeyFetcher{}, + KeyDatabase: serverKeyDB, + }, + } + + addDirectFetcher := func() { + internalAPI.OurKeyRing.KeyFetchers = append( + internalAPI.OurKeyRing.KeyFetchers, + &gomatrixserverlib.DirectKeyFetcher{ + Client: fedClient, + }, + ) + } + + if cfg.PreferDirectFetch { + addDirectFetcher() + } else { + defer addDirectFetcher() + } + + var b64e = base64.StdEncoding.WithPadding(base64.NoPadding) + for _, ps := range cfg.KeyPerspectives { + perspective := &gomatrixserverlib.PerspectiveKeyFetcher{ + PerspectiveServerName: ps.ServerName, + PerspectiveServerKeys: map[gomatrixserverlib.KeyID]ed25519.PublicKey{}, + Client: fedClient, + } + + for _, key := range ps.Keys { + rawkey, err := b64e.DecodeString(key.PublicKey) + if err != nil { + logrus.WithError(err).WithFields(logrus.Fields{ + "server_name": ps.ServerName, + "public_key": key.PublicKey, + }).Warn("Couldn't parse perspective key") + continue + } + perspective.PerspectiveServerKeys[key.KeyID] = rawkey + } + + internalAPI.OurKeyRing.KeyFetchers = append( + internalAPI.OurKeyRing.KeyFetchers, + perspective, + ) + + logrus.WithFields(logrus.Fields{ + "server_name": ps.ServerName, + "num_public_keys": len(ps.Keys), + }).Info("Enabled perspective key fetcher") + } + + return &internalAPI +} diff --git a/signingkeyserver/storage/cache/keydb.go b/signingkeyserver/storage/cache/keydb.go new file mode 100644 index 00000000..2063dfc5 --- /dev/null +++ b/signingkeyserver/storage/cache/keydb.go @@ -0,0 +1,68 @@ +package cache + +import ( + "context" + "errors" + + "github.com/matrix-org/dendrite/internal/caching" + "github.com/matrix-org/gomatrixserverlib" +) + +// A Database implements gomatrixserverlib.KeyDatabase and is used to store +// the public keys for other matrix servers. +type KeyDatabase struct { + inner gomatrixserverlib.KeyDatabase + cache caching.ServerKeyCache +} + +func NewKeyDatabase(inner gomatrixserverlib.KeyDatabase, cache caching.ServerKeyCache) (*KeyDatabase, error) { + if inner == nil { + return nil, errors.New("inner database can't be nil") + } + if cache == nil { + return nil, errors.New("cache can't be nil") + } + return &KeyDatabase{ + inner: inner, + cache: cache, + }, nil +} + +// FetcherName implements KeyFetcher +func (d KeyDatabase) FetcherName() string { + return "InMemoryKeyCache" +} + +// FetchKeys implements gomatrixserverlib.KeyDatabase +func (d *KeyDatabase) FetchKeys( + ctx context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { + results := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) + for req, ts := range requests { + if res, cached := d.cache.GetServerKey(req, ts); cached { + results[req] = res + delete(requests, req) + } + } + fromDB, err := d.inner.FetchKeys(ctx, requests) + if err != nil { + return results, err + } + for req, res := range fromDB { + results[req] = res + d.cache.StoreServerKey(req, res) + } + return results, nil +} + +// StoreKeys implements gomatrixserverlib.KeyDatabase +func (d *KeyDatabase) StoreKeys( + ctx context.Context, + keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, +) error { + for req, res := range keyMap { + d.cache.StoreServerKey(req, res) + } + return d.inner.StoreKeys(ctx, keyMap) +} diff --git a/signingkeyserver/storage/interface.go b/signingkeyserver/storage/interface.go new file mode 100644 index 00000000..3a67ac55 --- /dev/null +++ b/signingkeyserver/storage/interface.go @@ -0,0 +1,13 @@ +package storage + +import ( + "context" + + "github.com/matrix-org/gomatrixserverlib" +) + +type Database interface { + FetcherName() string + FetchKeys(ctx context.Context, requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) + StoreKeys(ctx context.Context, keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) error +} diff --git a/signingkeyserver/storage/keydb.go b/signingkeyserver/storage/keydb.go new file mode 100644 index 00000000..ef1077fc --- /dev/null +++ b/signingkeyserver/storage/keydb.go @@ -0,0 +1,45 @@ +// Copyright 2020 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. + +// +build !wasm + +package storage + +import ( + "fmt" + + "golang.org/x/crypto/ed25519" + + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/signingkeyserver/storage/postgres" + "github.com/matrix-org/dendrite/signingkeyserver/storage/sqlite3" + "github.com/matrix-org/gomatrixserverlib" +) + +// NewDatabase opens a database connection. +func NewDatabase( + dbProperties *config.DatabaseOptions, + serverName gomatrixserverlib.ServerName, + serverKey ed25519.PublicKey, + serverKeyID gomatrixserverlib.KeyID, +) (Database, error) { + switch { + case dbProperties.ConnectionString.IsSQLite(): + return sqlite3.NewDatabase(dbProperties, serverName, serverKey, serverKeyID) + case dbProperties.ConnectionString.IsPostgres(): + return postgres.NewDatabase(dbProperties, serverName, serverKey, serverKeyID) + default: + return nil, fmt.Errorf("unexpected database type") + } +} diff --git a/signingkeyserver/storage/keydb_wasm.go b/signingkeyserver/storage/keydb_wasm.go new file mode 100644 index 00000000..187d9669 --- /dev/null +++ b/signingkeyserver/storage/keydb_wasm.go @@ -0,0 +1,50 @@ +// Copyright 2020 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. + +// +build wasm + +package storage + +import ( + "fmt" + "net/url" + + "golang.org/x/crypto/ed25519" + + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/signingkeyserver/storage/sqlite3" + "github.com/matrix-org/gomatrixserverlib" +) + +// NewDatabase opens a database connection. +func NewDatabase( + dataSourceName string, + dbProperties sqlutil.DbProperties, // nolint:unparam + serverName gomatrixserverlib.ServerName, + serverKey ed25519.PublicKey, + serverKeyID gomatrixserverlib.KeyID, +) (Database, error) { + uri, err := url.Parse(dataSourceName) + if err != nil { + return nil, err + } + switch uri.Scheme { + case "postgres": + return nil, fmt.Errorf("Cannot use postgres implementation") + case "file": + return sqlite3.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID) + default: + return nil, fmt.Errorf("Cannot use postgres implementation") + } +} diff --git a/signingkeyserver/storage/postgres/keydb.go b/signingkeyserver/storage/postgres/keydb.go new file mode 100644 index 00000000..63444085 --- /dev/null +++ b/signingkeyserver/storage/postgres/keydb.go @@ -0,0 +1,91 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 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 postgres + +import ( + "context" + + "golang.org/x/crypto/ed25519" + + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/gomatrixserverlib" +) + +// A Database implements gomatrixserverlib.KeyDatabase and is used to store +// the public keys for other matrix servers. +type Database struct { + statements serverKeyStatements +} + +// NewDatabase prepares a new key database. +// It creates the necessary tables if they don't already exist. +// It prepares all the SQL statements that it will use. +// Returns an error if there was a problem talking to the database. +func NewDatabase( + dbProperties *config.DatabaseOptions, + serverName gomatrixserverlib.ServerName, + serverKey ed25519.PublicKey, + serverKeyID gomatrixserverlib.KeyID, +) (*Database, error) { + db, err := sqlutil.Open(dbProperties) + if err != nil { + return nil, err + } + d := &Database{} + err = d.statements.prepare(db) + if err != nil { + return nil, err + } + return d, nil +} + +// FetcherName implements KeyFetcher +func (d Database) FetcherName() string { + return "PostgresKeyDatabase" +} + +// FetchKeys implements gomatrixserverlib.KeyDatabase +func (d *Database) FetchKeys( + ctx context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { + return d.statements.bulkSelectServerKeys(ctx, requests) +} + +// StoreKeys implements gomatrixserverlib.KeyDatabase +func (d *Database) StoreKeys( + ctx context.Context, + keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, +) error { + // TODO: Inserting all the keys within a single transaction may + // be more efficient since the transaction overhead can be quite + // high for a single insert statement. + var lastErr error + for request, keys := range keyMap { + if err := d.statements.upsertServerKeys(ctx, request, keys); err != nil { + // Rather than returning immediately on error we try to insert the + // remaining keys. + // Since we are inserting the keys outside of a transaction it is + // possible for some of the inserts to succeed even though some + // of the inserts have failed. + // Ensuring that we always insert all the keys we can means that + // this behaviour won't depend on the iteration order of the map. + lastErr = err + } + } + return lastErr +} diff --git a/signingkeyserver/storage/postgres/server_key_table.go b/signingkeyserver/storage/postgres/server_key_table.go new file mode 100644 index 00000000..87f1c211 --- /dev/null +++ b/signingkeyserver/storage/postgres/server_key_table.go @@ -0,0 +1,143 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 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 postgres + +import ( + "context" + "database/sql" + + "github.com/lib/pq" + "github.com/matrix-org/dendrite/internal" + "github.com/matrix-org/gomatrixserverlib" +) + +const serverKeysSchema = ` +-- A cache of signing keys downloaded from remote servers. +CREATE TABLE IF NOT EXISTS keydb_server_keys ( + -- The name of the matrix server the key is for. + server_name TEXT NOT NULL, + -- The ID of the server key. + server_key_id TEXT NOT NULL, + -- Combined server name and key ID separated by the ASCII unit separator + -- to make it easier to run bulk queries. + server_name_and_key_id TEXT NOT NULL, + -- When the key is valid until as a millisecond timestamp. + -- 0 if this is an expired key (in which case expired_ts will be non-zero) + valid_until_ts BIGINT NOT NULL, + -- When the key expired as a millisecond timestamp. + -- 0 if this is an active key (in which case valid_until_ts will be non-zero) + expired_ts BIGINT NOT NULL, + -- The base64-encoded public key. + server_key TEXT NOT NULL, + CONSTRAINT keydb_server_keys_unique UNIQUE (server_name, server_key_id) +); + +CREATE INDEX IF NOT EXISTS keydb_server_name_and_key_id ON keydb_server_keys (server_name_and_key_id); +` + +const bulkSelectServerKeysSQL = "" + + "SELECT server_name, server_key_id, valid_until_ts, expired_ts, " + + " server_key FROM keydb_server_keys" + + " WHERE server_name_and_key_id = ANY($1)" + +const upsertServerKeysSQL = "" + + "INSERT INTO keydb_server_keys (server_name, server_key_id," + + " server_name_and_key_id, valid_until_ts, expired_ts, server_key)" + + " VALUES ($1, $2, $3, $4, $5, $6)" + + " ON CONFLICT ON CONSTRAINT keydb_server_keys_unique" + + " DO UPDATE SET valid_until_ts = $4, expired_ts = $5, server_key = $6" + +type serverKeyStatements struct { + bulkSelectServerKeysStmt *sql.Stmt + upsertServerKeysStmt *sql.Stmt +} + +func (s *serverKeyStatements) prepare(db *sql.DB) (err error) { + _, err = db.Exec(serverKeysSchema) + if err != nil { + return + } + if s.bulkSelectServerKeysStmt, err = db.Prepare(bulkSelectServerKeysSQL); err != nil { + return + } + if s.upsertServerKeysStmt, err = db.Prepare(upsertServerKeysSQL); err != nil { + return + } + return +} + +func (s *serverKeyStatements) bulkSelectServerKeys( + ctx context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { + var nameAndKeyIDs []string + for request := range requests { + nameAndKeyIDs = append(nameAndKeyIDs, nameAndKeyID(request)) + } + stmt := s.bulkSelectServerKeysStmt + rows, err := stmt.QueryContext(ctx, pq.StringArray(nameAndKeyIDs)) + if err != nil { + return nil, err + } + defer internal.CloseAndLogIfError(ctx, rows, "bulkSelectServerKeys: rows.close() failed") + results := map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{} + for rows.Next() { + var serverName string + var keyID string + var key string + var validUntilTS int64 + var expiredTS int64 + if err = rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil { + return nil, err + } + r := gomatrixserverlib.PublicKeyLookupRequest{ + ServerName: gomatrixserverlib.ServerName(serverName), + KeyID: gomatrixserverlib.KeyID(keyID), + } + vk := gomatrixserverlib.VerifyKey{} + err = vk.Key.Decode(key) + if err != nil { + return nil, err + } + results[r] = gomatrixserverlib.PublicKeyLookupResult{ + VerifyKey: vk, + ValidUntilTS: gomatrixserverlib.Timestamp(validUntilTS), + ExpiredTS: gomatrixserverlib.Timestamp(expiredTS), + } + } + return results, rows.Err() +} + +func (s *serverKeyStatements) upsertServerKeys( + ctx context.Context, + request gomatrixserverlib.PublicKeyLookupRequest, + key gomatrixserverlib.PublicKeyLookupResult, +) error { + _, err := s.upsertServerKeysStmt.ExecContext( + ctx, + string(request.ServerName), + string(request.KeyID), + nameAndKeyID(request), + key.ValidUntilTS, + key.ExpiredTS, + key.Key.Encode(), + ) + return err +} + +func nameAndKeyID(request gomatrixserverlib.PublicKeyLookupRequest) string { + return string(request.ServerName) + "\x1F" + string(request.KeyID) +} diff --git a/signingkeyserver/storage/sqlite3/keydb.go b/signingkeyserver/storage/sqlite3/keydb.go new file mode 100644 index 00000000..0ee74bc1 --- /dev/null +++ b/signingkeyserver/storage/sqlite3/keydb.go @@ -0,0 +1,99 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 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 sqlite3 + +import ( + "context" + + "golang.org/x/crypto/ed25519" + + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/gomatrixserverlib" + + _ "github.com/mattn/go-sqlite3" +) + +// A Database implements gomatrixserverlib.KeyDatabase and is used to store +// the public keys for other matrix servers. +type Database struct { + writer sqlutil.Writer + statements serverKeyStatements +} + +// NewDatabase prepares a new key database. +// It creates the necessary tables if they don't already exist. +// It prepares all the SQL statements that it will use. +// Returns an error if there was a problem talking to the database. +func NewDatabase( + dbProperties *config.DatabaseOptions, + serverName gomatrixserverlib.ServerName, + serverKey ed25519.PublicKey, + serverKeyID gomatrixserverlib.KeyID, +) (*Database, error) { + db, err := sqlutil.Open(dbProperties) + if err != nil { + return nil, err + } + d := &Database{ + writer: sqlutil.NewExclusiveWriter(), + } + err = d.statements.prepare(db, d.writer) + if err != nil { + return nil, err + } + if err != nil { + return nil, err + } + return d, nil +} + +// FetcherName implements KeyFetcher +func (d Database) FetcherName() string { + return "SqliteKeyDatabase" +} + +// FetchKeys implements gomatrixserverlib.KeyDatabase +func (d *Database) FetchKeys( + ctx context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { + return d.statements.bulkSelectServerKeys(ctx, requests) +} + +// StoreKeys implements gomatrixserverlib.KeyDatabase +func (d *Database) StoreKeys( + ctx context.Context, + keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, +) error { + // TODO: Inserting all the keys within a single transaction may + // be more efficient since the transaction overhead can be quite + // high for a single insert statement. + var lastErr error + for request, keys := range keyMap { + if err := d.statements.upsertServerKeys(ctx, request, keys); err != nil { + // Rather than returning immediately on error we try to insert the + // remaining keys. + // Since we are inserting the keys outside of a transaction it is + // possible for some of the inserts to succeed even though some + // of the inserts have failed. + // Ensuring that we always insert all the keys we can means that + // this behaviour won't depend on the iteration order of the map. + lastErr = err + } + } + return lastErr +} diff --git a/signingkeyserver/storage/sqlite3/server_key_table.go b/signingkeyserver/storage/sqlite3/server_key_table.go new file mode 100644 index 00000000..2484d636 --- /dev/null +++ b/signingkeyserver/storage/sqlite3/server_key_table.go @@ -0,0 +1,159 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 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 sqlite3 + +import ( + "context" + "database/sql" + "fmt" + + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/gomatrixserverlib" +) + +const serverKeysSchema = ` +-- A cache of signing keys downloaded from remote servers. +CREATE TABLE IF NOT EXISTS keydb_server_keys ( + -- The name of the matrix server the key is for. + server_name TEXT NOT NULL, + -- The ID of the server key. + server_key_id TEXT NOT NULL, + -- Combined server name and key ID separated by the ASCII unit separator + -- to make it easier to run bulk queries. + server_name_and_key_id TEXT NOT NULL, + -- When the key is valid until as a millisecond timestamp. + -- 0 if this is an expired key (in which case expired_ts will be non-zero) + valid_until_ts BIGINT NOT NULL, + -- When the key expired as a millisecond timestamp. + -- 0 if this is an active key (in which case valid_until_ts will be non-zero) + expired_ts BIGINT NOT NULL, + -- The base64-encoded public key. + server_key TEXT NOT NULL, + UNIQUE (server_name, server_key_id) +); + +CREATE INDEX IF NOT EXISTS keydb_server_name_and_key_id ON keydb_server_keys (server_name_and_key_id); +` + +const bulkSelectServerKeysSQL = "" + + "SELECT server_name, server_key_id, valid_until_ts, expired_ts, " + + " server_key FROM keydb_server_keys" + + " WHERE server_name_and_key_id IN ($1)" + +const upsertServerKeysSQL = "" + + "INSERT INTO keydb_server_keys (server_name, server_key_id," + + " server_name_and_key_id, valid_until_ts, expired_ts, server_key)" + + " VALUES ($1, $2, $3, $4, $5, $6)" + + " ON CONFLICT (server_name, server_key_id)" + + " DO UPDATE SET valid_until_ts = $4, expired_ts = $5, server_key = $6" + +type serverKeyStatements struct { + db *sql.DB + writer sqlutil.Writer + bulkSelectServerKeysStmt *sql.Stmt + upsertServerKeysStmt *sql.Stmt +} + +func (s *serverKeyStatements) prepare(db *sql.DB, writer sqlutil.Writer) (err error) { + s.db = db + s.writer = writer + _, err = db.Exec(serverKeysSchema) + if err != nil { + return + } + if s.bulkSelectServerKeysStmt, err = db.Prepare(bulkSelectServerKeysSQL); err != nil { + return + } + if s.upsertServerKeysStmt, err = db.Prepare(upsertServerKeysSQL); err != nil { + return + } + return +} + +func (s *serverKeyStatements) bulkSelectServerKeys( + ctx context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { + nameAndKeyIDs := make([]string, 0, len(requests)) + for request := range requests { + nameAndKeyIDs = append(nameAndKeyIDs, nameAndKeyID(request)) + } + results := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, len(requests)) + iKeyIDs := make([]interface{}, len(nameAndKeyIDs)) + for i, v := range nameAndKeyIDs { + iKeyIDs[i] = v + } + + err := sqlutil.RunLimitedVariablesQuery( + ctx, bulkSelectServerKeysSQL, s.db, iKeyIDs, sqlutil.SQLite3MaxVariables, + func(rows *sql.Rows) error { + for rows.Next() { + var serverName string + var keyID string + var key string + var validUntilTS int64 + var expiredTS int64 + if err := rows.Scan(&serverName, &keyID, &validUntilTS, &expiredTS, &key); err != nil { + return fmt.Errorf("bulkSelectServerKeys: %v", err) + } + r := gomatrixserverlib.PublicKeyLookupRequest{ + ServerName: gomatrixserverlib.ServerName(serverName), + KeyID: gomatrixserverlib.KeyID(keyID), + } + vk := gomatrixserverlib.VerifyKey{} + err := vk.Key.Decode(key) + if err != nil { + return fmt.Errorf("bulkSelectServerKeys: %v", err) + } + results[r] = gomatrixserverlib.PublicKeyLookupResult{ + VerifyKey: vk, + ValidUntilTS: gomatrixserverlib.Timestamp(validUntilTS), + ExpiredTS: gomatrixserverlib.Timestamp(expiredTS), + } + } + return nil + }, + ) + + if err != nil { + return nil, err + } + return results, nil +} + +func (s *serverKeyStatements) upsertServerKeys( + ctx context.Context, + request gomatrixserverlib.PublicKeyLookupRequest, + key gomatrixserverlib.PublicKeyLookupResult, +) error { + return s.writer.Do(s.db, nil, func(txn *sql.Tx) error { + stmt := sqlutil.TxStmt(txn, s.upsertServerKeysStmt) + _, err := stmt.ExecContext( + ctx, + string(request.ServerName), + string(request.KeyID), + nameAndKeyID(request), + key.ValidUntilTS, + key.ExpiredTS, + key.Key.Encode(), + ) + return err + }) +} + +func nameAndKeyID(request gomatrixserverlib.PublicKeyLookupRequest) string { + return string(request.ServerName) + "\x1F" + string(request.KeyID) +} -- cgit v1.2.3 From 8bca7a83a98a310e4adae405d125dda93c8db1a0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Oct 2020 16:59:22 +0100 Subject: Update monolith -api behaviour (#1484) * Update monolith -api mode listeners * Fix check * Fix another check * Update HTTP API addr behaviour * Redefine NoExternalListener * NoListener --- cmd/dendrite-appservice-server/main.go | 4 +- cmd/dendrite-edu-server/main.go | 4 +- cmd/dendrite-federation-sender-server/main.go | 4 +- cmd/dendrite-key-server/main.go | 4 +- cmd/dendrite-monolith-server/main.go | 37 +++++++++-------- cmd/dendrite-room-server/main.go | 4 +- cmd/dendrite-signing-key-server/main.go | 2 +- cmd/dendrite-user-api-server/main.go | 4 +- internal/setup/base.go | 59 ++++++++++++++------------- 9 files changed, 64 insertions(+), 58 deletions(-) diff --git a/cmd/dendrite-appservice-server/main.go b/cmd/dendrite-appservice-server/main.go index 72b243e2..6adbdb17 100644 --- a/cmd/dendrite-appservice-server/main.go +++ b/cmd/dendrite-appservice-server/main.go @@ -31,8 +31,8 @@ func main() { appservice.AddInternalRoutes(base.InternalAPIMux, intAPI) base.SetupAndServeHTTP( - base.Cfg.AppServiceAPI.InternalAPI.Listen, - setup.NoExternalListener, + base.Cfg.AppServiceAPI.InternalAPI.Listen, // internal listener + setup.NoListener, // external listener nil, nil, ) } diff --git a/cmd/dendrite-edu-server/main.go b/cmd/dendrite-edu-server/main.go index e0956619..3a34b9a6 100644 --- a/cmd/dendrite-edu-server/main.go +++ b/cmd/dendrite-edu-server/main.go @@ -34,8 +34,8 @@ func main() { eduserver.AddInternalRoutes(base.InternalAPIMux, intAPI) base.SetupAndServeHTTP( - base.Cfg.EDUServer.InternalAPI.Listen, - setup.NoExternalListener, + base.Cfg.EDUServer.InternalAPI.Listen, // internal listener + setup.NoListener, // external listener nil, nil, ) } diff --git a/cmd/dendrite-federation-sender-server/main.go b/cmd/dendrite-federation-sender-server/main.go index 07380bb0..99b416c4 100644 --- a/cmd/dendrite-federation-sender-server/main.go +++ b/cmd/dendrite-federation-sender-server/main.go @@ -36,8 +36,8 @@ func main() { federationsender.AddInternalRoutes(base.InternalAPIMux, fsAPI) base.SetupAndServeHTTP( - base.Cfg.FederationSender.InternalAPI.Listen, - setup.NoExternalListener, + base.Cfg.FederationSender.InternalAPI.Listen, // internal listener + setup.NoListener, // external listener nil, nil, ) } diff --git a/cmd/dendrite-key-server/main.go b/cmd/dendrite-key-server/main.go index 2110b216..92d18ac3 100644 --- a/cmd/dendrite-key-server/main.go +++ b/cmd/dendrite-key-server/main.go @@ -30,8 +30,8 @@ func main() { keyserver.AddInternalRoutes(base.InternalAPIMux, intAPI) base.SetupAndServeHTTP( - base.Cfg.KeyServer.InternalAPI.Listen, - setup.NoExternalListener, + base.Cfg.KeyServer.InternalAPI.Listen, // internal listener + setup.NoListener, // external listener nil, nil, ) } diff --git a/cmd/dendrite-monolith-server/main.go b/cmd/dendrite-monolith-server/main.go index c50c0c21..0fe70ca8 100644 --- a/cmd/dendrite-monolith-server/main.go +++ b/cmd/dendrite-monolith-server/main.go @@ -29,11 +29,13 @@ import ( "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/signingkeyserver" "github.com/matrix-org/dendrite/userapi" + "github.com/sirupsen/logrus" ) var ( httpBindAddr = flag.String("http-bind-address", ":8008", "The HTTP listening port for the server") httpsBindAddr = flag.String("https-bind-address", ":8448", "The HTTPS listening port for the server") + apiBindAddr = flag.String("api-bind-address", "localhost:18008", "The HTTP listening port for the internal HTTP APIs (if -api is enabled)") certFile = flag.String("tls-cert", "", "The PEM formatted X509 certificate to use for TLS") keyFile = flag.String("tls-key", "", "The PEM private key to use for TLS") enableHTTPAPIs = flag.Bool("api", false, "Use HTTP APIs instead of short-circuiting (warning: exposes API endpoints!)") @@ -44,22 +46,25 @@ func main() { cfg := setup.ParseFlags(true) httpAddr := config.HTTPAddress("http://" + *httpBindAddr) httpsAddr := config.HTTPAddress("https://" + *httpsBindAddr) + httpAPIAddr := httpAddr if *enableHTTPAPIs { + logrus.Warnf("DANGER! The -api option is enabled, exposing internal APIs on %q!", *apiBindAddr) + httpAPIAddr = config.HTTPAddress("http://" + *apiBindAddr) // If the HTTP APIs are enabled then we need to update the Listen // statements in the configuration so that we know where to find // the API endpoints. They'll listen on the same port as the monolith // itself. - cfg.AppServiceAPI.InternalAPI.Connect = httpAddr - cfg.ClientAPI.InternalAPI.Connect = httpAddr - cfg.EDUServer.InternalAPI.Connect = httpAddr - cfg.FederationAPI.InternalAPI.Connect = httpAddr - cfg.FederationSender.InternalAPI.Connect = httpAddr - cfg.KeyServer.InternalAPI.Connect = httpAddr - cfg.MediaAPI.InternalAPI.Connect = httpAddr - cfg.RoomServer.InternalAPI.Connect = httpAddr - cfg.SigningKeyServer.InternalAPI.Connect = httpAddr - cfg.SyncAPI.InternalAPI.Connect = httpAddr + cfg.AppServiceAPI.InternalAPI.Connect = httpAPIAddr + cfg.ClientAPI.InternalAPI.Connect = httpAPIAddr + cfg.EDUServer.InternalAPI.Connect = httpAPIAddr + cfg.FederationAPI.InternalAPI.Connect = httpAPIAddr + cfg.FederationSender.InternalAPI.Connect = httpAPIAddr + cfg.KeyServer.InternalAPI.Connect = httpAPIAddr + cfg.MediaAPI.InternalAPI.Connect = httpAPIAddr + cfg.RoomServer.InternalAPI.Connect = httpAPIAddr + cfg.SigningKeyServer.InternalAPI.Connect = httpAPIAddr + cfg.SyncAPI.InternalAPI.Connect = httpAPIAddr } base := setup.NewBaseDendrite(cfg, "Monolith", *enableHTTPAPIs) @@ -148,18 +153,18 @@ func main() { // Expose the matrix APIs directly rather than putting them under a /api path. go func() { base.SetupAndServeHTTP( - config.HTTPAddress(httpAddr), // internal API - config.HTTPAddress(httpAddr), // external API - nil, nil, // TLS settings + httpAPIAddr, // internal API + httpAddr, // external API + nil, nil, // TLS settings ) }() // Handle HTTPS if certificate and key are provided if *certFile != "" && *keyFile != "" { go func() { base.SetupAndServeHTTP( - config.HTTPAddress(httpsAddr), // internal API - config.HTTPAddress(httpsAddr), // external API - certFile, keyFile, // TLS settings + setup.NoListener, // internal API + httpsAddr, // external API + certFile, keyFile, // TLS settings ) }() } diff --git a/cmd/dendrite-room-server/main.go b/cmd/dendrite-room-server/main.go index c61368bf..d3f14574 100644 --- a/cmd/dendrite-room-server/main.go +++ b/cmd/dendrite-room-server/main.go @@ -33,8 +33,8 @@ func main() { roomserver.AddInternalRoutes(base.InternalAPIMux, rsAPI) base.SetupAndServeHTTP( - base.Cfg.RoomServer.InternalAPI.Listen, - setup.NoExternalListener, + base.Cfg.RoomServer.InternalAPI.Listen, // internal listener + setup.NoListener, // external listener nil, nil, ) } diff --git a/cmd/dendrite-signing-key-server/main.go b/cmd/dendrite-signing-key-server/main.go index 003bd755..a4d48d36 100644 --- a/cmd/dendrite-signing-key-server/main.go +++ b/cmd/dendrite-signing-key-server/main.go @@ -31,7 +31,7 @@ func main() { base.SetupAndServeHTTP( base.Cfg.SigningKeyServer.InternalAPI.Listen, - setup.NoExternalListener, + setup.NoListener, nil, nil, ) } diff --git a/cmd/dendrite-user-api-server/main.go b/cmd/dendrite-user-api-server/main.go index c8e2e2a3..fb65fefb 100644 --- a/cmd/dendrite-user-api-server/main.go +++ b/cmd/dendrite-user-api-server/main.go @@ -31,8 +31,8 @@ func main() { userapi.AddInternalRoutes(base.InternalAPIMux, userAPI) base.SetupAndServeHTTP( - base.Cfg.UserAPI.InternalAPI.Listen, - setup.NoExternalListener, + base.Cfg.UserAPI.InternalAPI.Listen, // internal listener + setup.NoListener, // external listener nil, nil, ) } diff --git a/internal/setup/base.go b/internal/setup/base.go index 6a0a8bbd..77fdb04a 100644 --- a/internal/setup/base.go +++ b/internal/setup/base.go @@ -80,7 +80,7 @@ type BaseDendrite struct { const HTTPServerTimeout = time.Minute * 5 const HTTPClientTimeout = time.Second * 30 -const NoExternalListener = "" +const NoListener = "" // NewBaseDendrite creates a new instance to be used by a component. // The componentName is used for logging purposes, and should be a friendly name @@ -272,22 +272,21 @@ func (b *BaseDendrite) SetupAndServeHTTP( internalAddr, _ := internalHTTPAddr.Address() externalAddr, _ := externalHTTPAddr.Address() - internalRouter := mux.NewRouter().SkipClean(true).UseEncodedPath() - externalRouter := internalRouter + externalRouter := mux.NewRouter().SkipClean(true).UseEncodedPath() + internalRouter := externalRouter - internalServ := &http.Server{ - Addr: string(internalAddr), + externalServ := &http.Server{ + Addr: string(externalAddr), WriteTimeout: HTTPServerTimeout, - Handler: internalRouter, + Handler: externalRouter, } - externalServ := internalServ - - if externalAddr != NoExternalListener && externalAddr != internalAddr { - externalRouter = mux.NewRouter().SkipClean(true).UseEncodedPath() - externalServ = &http.Server{ - Addr: string(externalAddr), - WriteTimeout: HTTPServerTimeout, - Handler: externalRouter, + internalServ := externalServ + + if internalAddr != NoListener && externalAddr != internalAddr { + internalRouter = mux.NewRouter().SkipClean(true).UseEncodedPath() + internalServ = &http.Server{ + Addr: string(internalAddr), + Handler: internalRouter, } } @@ -301,23 +300,25 @@ func (b *BaseDendrite) SetupAndServeHTTP( externalRouter.PathPrefix(httputil.PublicFederationPathPrefix).Handler(b.PublicFederationAPIMux) externalRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(b.PublicMediaAPIMux) - go func() { - logrus.Infof("Starting %s listener on %s", b.componentName, internalServ.Addr) - if certFile != nil && keyFile != nil { - if err := internalServ.ListenAndServeTLS(*certFile, *keyFile); err != nil { - logrus.WithError(err).Fatal("failed to serve HTTPS") - } - } else { - if err := internalServ.ListenAndServe(); err != nil { - logrus.WithError(err).Fatal("failed to serve HTTP") + if internalAddr != NoListener && internalAddr != externalAddr { + go func() { + logrus.Infof("Starting internal %s listener on %s", b.componentName, internalServ.Addr) + if certFile != nil && keyFile != nil { + if err := internalServ.ListenAndServeTLS(*certFile, *keyFile); err != nil { + logrus.WithError(err).Fatal("failed to serve HTTPS") + } + } else { + if err := internalServ.ListenAndServe(); err != nil { + logrus.WithError(err).Fatal("failed to serve HTTP") + } } - } - logrus.Infof("Stopped %s listener on %s", b.componentName, internalServ.Addr) - }() + logrus.Infof("Stopped internal %s listener on %s", b.componentName, internalServ.Addr) + }() + } - if externalAddr != NoExternalListener && internalAddr != externalAddr { + if externalAddr != NoListener { go func() { - logrus.Infof("Starting %s listener on %s", b.componentName, externalServ.Addr) + logrus.Infof("Starting external %s listener on %s", b.componentName, externalServ.Addr) if certFile != nil && keyFile != nil { if err := externalServ.ListenAndServeTLS(*certFile, *keyFile); err != nil { logrus.WithError(err).Fatal("failed to serve HTTPS") @@ -327,7 +328,7 @@ func (b *BaseDendrite) SetupAndServeHTTP( logrus.WithError(err).Fatal("failed to serve HTTP") } } - logrus.Infof("Stopped %s listener on %s", b.componentName, externalServ.Addr) + logrus.Infof("Stopped external %s listener on %s", b.componentName, externalServ.Addr) }() } -- cgit v1.2.3