161f145176
* Add NATS JetStream support Update shopify/sarama * Fix addresses * Don't change Addresses in Defaults * Update saramajetstream * Add missing error check Keep typing events for at least one minute * Use all configured NATS addresses * Update saramajetstream * Try setting up with NATS * Make sure NATS uses own persistent directory (TODO: make this configurable) * Update go.mod/go.sum * Jetstream package * Various other refactoring * Build fixes * Config tweaks, make random jetstream storage path for CI * Disable interest policies * Try to sane default on jetstream base path * Try to use in-memory for CI * Restore storage/retention * Update nats.go dependency * Adapt changes to config * Remove unneeded TopicFor * Dep update * Revert "Remove unneeded TopicFor" This reverts commitf5a4e4a339
. * Revert changes made to streams * Fix build problems * Update nats-server * Update go.mod/go.sum * Roomserver input API queuing using NATS * Fix topic naming * Prometheus metrics * More refactoring to remove saramajetstream * Add missing topic * Don't try to populate map that doesn't exist * Roomserver output topic * Update go.mod/go.sum * Message acknowledgements * Ack tweaks * Try to resume transaction re-sends * Try to resume transaction re-sends * Update to matrix-org/gomatrixserverlib@91dadfb * Remove internal.PartitionStorer from components that don't consume keychanges * Try to reduce re-allocations a bit in resolveConflictsV2 * Tweak delivery options on RS input * Publish send-to-device messages into correct JetStream subject * Async and sync roomserver input * Update dendrite-config.yaml * Remove roomserver tests for now (they need rewriting) * Remove roomserver test again (was merged back in) * Update documentation * Docker updates * More Docker updates * Update Docker readme again * Fix lint issues * Send final event in `processEvent` synchronously (since this might stop Sytest from being so upset) * Don't report event rejection errors via `/send`, since apparently this is upsetting tests that don't expect that * Go 1.16 instead of Go 1.13 for upgrade tests and Complement * Revert "Don't report event rejection errors via `/send`, since apparently this is upsetting tests that don't expect that" This reverts commit368675283f
. * Don't report any errors on `/send` to see what fun that creates * Fix panics on closed channel sends * Enforce state key matches sender * Do the same for leave * Various tweaks to make tests happier Squashed commit of the following: commit13f9028e7a
Author: Neil Alexander <neilalexander@users.noreply.github.com> Date: Tue Jan 4 15:47:14 2022 +0000 Do the same for leave commite6be7f05c3
Author: Neil Alexander <neilalexander@users.noreply.github.com> Date: Tue Jan 4 15:33:42 2022 +0000 Enforce state key matches sender commit85ede6d64b
Author: Neil Alexander <neilalexander@users.noreply.github.com> Date: Tue Jan 4 14:07:04 2022 +0000 Fix panics on closed channel sends commit9755494a98
Author: Neil Alexander <neilalexander@users.noreply.github.com> Date: Tue Jan 4 13:38:22 2022 +0000 Don't report any errors on `/send` to see what fun that creates commit3bb4f87b5d
Author: Neil Alexander <neilalexander@users.noreply.github.com> Date: Tue Jan 4 13:00:26 2022 +0000 Revert "Don't report event rejection errors via `/send`, since apparently this is upsetting tests that don't expect that" This reverts commit368675283f
. commitfe2673ed7b
Author: Neil Alexander <neilalexander@users.noreply.github.com> Date: Tue Jan 4 12:09:34 2022 +0000 Go 1.16 instead of Go 1.13 for upgrade tests and Complement commit368675283f
Author: Neil Alexander <neilalexander@users.noreply.github.com> Date: Tue Jan 4 11:51:45 2022 +0000 Don't report event rejection errors via `/send`, since apparently this is upsetting tests that don't expect that commitb028dfc085
Author: Neil Alexander <neilalexander@users.noreply.github.com> Date: Tue Jan 4 10:29:08 2022 +0000 Send final event in `processEvent` synchronously (since this might stop Sytest from being so upset) * Merge in NATS Server v2.6.6 and nats.go v1.13 into the in-process connection fork * Add `jetstream.WithJetStreamMessage` to make ack/nak-ing less messy, use process context in consumers * Fix consumer component name in federation API * Add comment explaining where streams are defined * Tweaks to roomserver input with comments * Finish that sentence that I apparently forgot to finish in INSTALL.md * Bump version number of config to 2 * Add comments around asynchronous sends to roomserver in processEventWithMissingState * More useful error message when the config version does not match * Set version in generate-config * Fix version in config.Defaults Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
321 lines
10 KiB
Go
321 lines
10 KiB
Go
package federationapi
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ed25519"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/matrix-org/dendrite/federationapi/api"
|
|
"github.com/matrix-org/dendrite/federationapi/routing"
|
|
"github.com/matrix-org/dendrite/internal/caching"
|
|
"github.com/matrix-org/dendrite/setup/base"
|
|
"github.com/matrix-org/dendrite/setup/config"
|
|
"github.com/matrix-org/gomatrixserverlib"
|
|
)
|
|
|
|
type server struct {
|
|
name gomatrixserverlib.ServerName // server name
|
|
validity time.Duration // key validity duration from now
|
|
config *config.FederationAPI // skeleton config, from TestMain
|
|
fedclient *gomatrixserverlib.FederationClient // uses MockRoundTripper
|
|
cache *caching.Caches // server-specific cache
|
|
api api.FederationInternalAPI // 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(true)
|
|
cfg.Global.ServerName = gomatrixserverlib.ServerName(s.name)
|
|
cfg.Global.PrivateKey = testPriv
|
|
cfg.Global.JetStream.InMemory = true
|
|
cfg.Global.KeyID = serverKeyID
|
|
cfg.Global.KeyValidityPeriod = s.validity
|
|
cfg.FederationAPI.Database.ConnectionString = config.DataSource("file::memory:")
|
|
s.config = &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.NewFederationClient(
|
|
s.config.Matrix.ServerName, serverKeyID, testPriv,
|
|
gomatrixserverlib.WithTransport(transport),
|
|
)
|
|
|
|
// Finally, build the server key APIs.
|
|
sbase := base.NewBaseDendrite(cfg, "Monolith", base.NoCacheMetrics)
|
|
s.api = NewInternalAPI(sbase, s.fedclient, nil, s.cache, nil, true)
|
|
}
|
|
|
|
// 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.config)
|
|
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)
|
|
}
|