diff options
author | Neil Alexander <neilalexander@users.noreply.github.com> | 2020-06-16 13:11:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-16 13:11:20 +0100 |
commit | 57b7fa3db801c27190bfd143cfebe98e3d76a6ae (patch) | |
tree | 0bc045b74be45987855dd2cf7358c83955cad7b0 /serverkeyapi/serverkeyapi_test.go | |
parent | 67ad6618139a495a80800a2145d9ba319c5d0c5d (diff) |
More server key updates, tests (#1129)
* More key tweaks
* Start testing stuff
* Move responsibility for generating local keys into server key API, don't register prom in caches unless needed, start tests
* Don't store our own keys in the database
* Don't store our own keys in the database
* Don't run tests for now
* Tweak caching behaviour, update tests
* Update comments, add fixes from forward-merge
* Debug logging
* Debug logging
* Perform final comparison against original set of requests
* oops
* Fetcher timeouts
* Fetcher timeouts
* missing func
* Tweaks
* Update gomatrixserverlib
* Fix Federation API test
* Break up FetchKeys
* Add comments to caching
* Add URL check in test
* Partially revert "Move responsibility for generating local keys into server key API, don't register prom in caches unless needed, start tests"
This reverts commit d7eb54c5b30b2f6a9d6514b643e32e6ad2b602f3.
* Fix federation API test
* Fix internal cache stuff again
* Fix server key API test
* Update comments
* Update comments from review
* Fix lint
Diffstat (limited to 'serverkeyapi/serverkeyapi_test.go')
-rw-r--r-- | serverkeyapi/serverkeyapi_test.go | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/serverkeyapi/serverkeyapi_test.go b/serverkeyapi/serverkeyapi_test.go new file mode 100644 index 00000000..3368f5b2 --- /dev/null +++ b/serverkeyapi/serverkeyapi_test.go @@ -0,0 +1,315 @@ +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.Dendrite // skeleton config, from TestMain + 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. + s.config = &config.Dendrite{} + s.config.SetDefaults() + s.config.Matrix.ServerName = gomatrixserverlib.ServerName(s.name) + s.config.Matrix.PrivateKey = testPriv + s.config.Matrix.KeyID = serverKeyID + s.config.Matrix.KeyValidityPeriod = s.validity + s.config.Database.ServerKey = config.DataSource("file::memory:") + + // 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, 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.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) +} |