Move responsibility for generating local keys into server key API, don't register prom in caches unless needed, start tests

This commit is contained in:
Neil Alexander 2020-06-15 14:05:57 +01:00
parent 81f4ef5e09
commit d7eb54c5b3
16 changed files with 332 additions and 166 deletions

View file

@ -27,15 +27,14 @@ func main() {
accountDB := base.CreateAccountsDB() accountDB := base.CreateAccountsDB()
deviceDB := base.CreateDeviceDB() deviceDB := base.CreateDeviceDB()
federation := base.CreateFederationClient() federation := base.CreateFederationClient()
serverKeyAPI := base.ServerKeyAPIClient()
keyRing := serverKeyAPI.KeyRing()
fsAPI := base.FederationSenderHTTPClient() fsAPI := base.FederationSenderHTTPClient()
rsAPI := base.RoomserverHTTPClient() rsAPI := base.RoomserverHTTPClient()
asAPI := base.AppserviceHTTPClient() asAPI := base.AppserviceHTTPClient()
skAPI := base.ServerKeyAPIClient()
federationapi.AddPublicRoutes( federationapi.AddPublicRoutes(
base.PublicAPIMux, base.Cfg, accountDB, deviceDB, federation, keyRing, base.PublicAPIMux, base.Cfg, accountDB, deviceDB, federation,
rsAPI, asAPI, fsAPI, base.EDUServerClient(), skAPI, rsAPI, asAPI, fsAPI, base.EDUServerClient(),
) )
base.SetupAndServeHTTP(string(base.Cfg.Bind.FederationAPI), string(base.Cfg.Listen.FederationAPI)) base.SetupAndServeHTTP(string(base.Cfg.Bind.FederationAPI), string(base.Cfg.Listen.FederationAPI))

View file

@ -255,7 +255,7 @@ func testRoomserver(input []string, wantOutput []string, checkQueries func(api.R
panic(err) panic(err)
} }
cache, err := caching.NewInMemoryLRUCache() cache, err := caching.NewInMemoryLRUCache(false)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -23,6 +23,7 @@ import (
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api" federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
"github.com/matrix-org/dendrite/internal/config" "github.com/matrix-org/dendrite/internal/config"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
serverkeyAPI "github.com/matrix-org/dendrite/serverkeyapi/api"
"github.com/matrix-org/dendrite/federationapi/routing" "github.com/matrix-org/dendrite/federationapi/routing"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
@ -35,16 +36,15 @@ func AddPublicRoutes(
accountsDB accounts.Database, accountsDB accounts.Database,
deviceDB devices.Database, deviceDB devices.Database,
federation *gomatrixserverlib.FederationClient, federation *gomatrixserverlib.FederationClient,
keyRing *gomatrixserverlib.KeyRing, skAPI serverkeyAPI.ServerKeyInternalAPI,
rsAPI roomserverAPI.RoomserverInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
federationSenderAPI federationSenderAPI.FederationSenderInternalAPI, federationSenderAPI federationSenderAPI.FederationSenderInternalAPI,
eduAPI eduserverAPI.EDUServerInputAPI, eduAPI eduserverAPI.EDUServerInputAPI,
) { ) {
routing.Setup( routing.Setup(
router, cfg, rsAPI, asAPI, router, cfg, rsAPI, asAPI,
eduAPI, federationSenderAPI, *keyRing, eduAPI, federationSenderAPI, skAPI,
federation, accountsDB, deviceDB, federation, accountsDB, deviceDB,
) )
} }

View file

@ -1,68 +0,0 @@
// Copyright 2017 Vector Creations Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"encoding/json"
"net/http"
"time"
"github.com/matrix-org/dendrite/internal/config"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
"golang.org/x/crypto/ed25519"
)
// LocalKeys returns the local keys for the server.
// See https://matrix.org/docs/spec/server_server/unstable.html#publishing-keys
func LocalKeys(cfg *config.Dendrite) util.JSONResponse {
keys, err := localKeys(cfg, time.Now().Add(cfg.Matrix.KeyValidityPeriod))
if err != nil {
return util.ErrorResponse(err)
}
return util.JSONResponse{Code: http.StatusOK, JSON: keys}
}
func localKeys(cfg *config.Dendrite, validUntil time.Time) (*gomatrixserverlib.ServerKeys, error) {
var keys gomatrixserverlib.ServerKeys
keys.ServerName = cfg.Matrix.ServerName
publicKey := cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey)
keys.VerifyKeys = map[gomatrixserverlib.KeyID]gomatrixserverlib.VerifyKey{
cfg.Matrix.KeyID: {
Key: gomatrixserverlib.Base64Bytes(publicKey),
},
}
keys.TLSFingerprints = cfg.Matrix.TLSFingerPrints
keys.OldVerifyKeys = map[gomatrixserverlib.KeyID]gomatrixserverlib.OldVerifyKey{}
keys.ValidUntilTS = gomatrixserverlib.AsTimestamp(validUntil)
toSign, err := json.Marshal(keys.ServerKeyFields)
if err != nil {
return nil, err
}
keys.Raw, err = gomatrixserverlib.SignJSON(
string(cfg.Matrix.ServerName), cfg.Matrix.KeyID, cfg.Matrix.PrivateKey, toSign,
)
if err != nil {
return nil, err
}
return &keys, nil
}

View file

@ -26,6 +26,7 @@ import (
"github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/internal/config" "github.com/matrix-org/dendrite/internal/config"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
serverkeyAPI "github.com/matrix-org/dendrite/serverkeyapi/api"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -51,11 +52,12 @@ func Setup(
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
eduAPI eduserverAPI.EDUServerInputAPI, eduAPI eduserverAPI.EDUServerInputAPI,
fsAPI federationSenderAPI.FederationSenderInternalAPI, fsAPI federationSenderAPI.FederationSenderInternalAPI,
keys gomatrixserverlib.KeyRing, skAPI serverkeyAPI.ServerKeyInternalAPI,
federation *gomatrixserverlib.FederationClient, federation *gomatrixserverlib.FederationClient,
accountDB accounts.Database, accountDB accounts.Database,
deviceDB devices.Database, deviceDB devices.Database,
) { ) {
keys := *skAPI.KeyRing()
v2keysmux := publicAPIMux.PathPrefix(pathPrefixV2Keys).Subrouter() v2keysmux := publicAPIMux.PathPrefix(pathPrefixV2Keys).Subrouter()
v1fedmux := publicAPIMux.PathPrefix(pathPrefixV1Federation).Subrouter() v1fedmux := publicAPIMux.PathPrefix(pathPrefixV1Federation).Subrouter()
v2fedmux := publicAPIMux.PathPrefix(pathPrefixV2Federation).Subrouter() v2fedmux := publicAPIMux.PathPrefix(pathPrefixV2Federation).Subrouter()
@ -65,7 +67,15 @@ func Setup(
} }
localKeys := internal.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse { localKeys := internal.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse {
return LocalKeys(cfg) request := &serverkeyAPI.QueryLocalKeysRequest{}
response := &serverkeyAPI.QueryLocalKeysResponse{}
if err := skAPI.QueryLocalKeys(req.Context(), request, response); err != nil {
return util.ErrorResponse(err)
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: response.ServerKeys,
}
}) })
// Ignore the {keyID} argument as we only have a single server key so we always // Ignore the {keyID} argument as we only have a single server key so we always

View file

@ -95,7 +95,7 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, useHTTPAPIs boo
kafkaConsumer, kafkaProducer = setupKafka(cfg) kafkaConsumer, kafkaProducer = setupKafka(cfg)
} }
cache, err := caching.NewInMemoryLRUCache() cache, err := caching.NewInMemoryLRUCache(true)
if err != nil { if err != nil {
logrus.WithError(err).Warnf("Failed to create cache") logrus.WithError(err).Warnf("Failed to create cache")
} }

View file

@ -8,11 +8,12 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
) )
func NewInMemoryLRUCache() (*Caches, error) { func NewInMemoryLRUCache(enablePrometheus bool) (*Caches, error) {
roomVersions, err := NewInMemoryLRUCachePartition( roomVersions, err := NewInMemoryLRUCachePartition(
RoomVersionCacheName, RoomVersionCacheName,
RoomVersionCacheMutable, RoomVersionCacheMutable,
RoomVersionCacheMaxEntries, RoomVersionCacheMaxEntries,
enablePrometheus,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -21,6 +22,7 @@ func NewInMemoryLRUCache() (*Caches, error) {
ServerKeyCacheName, ServerKeyCacheName,
ServerKeyCacheMutable, ServerKeyCacheMutable,
ServerKeyCacheMaxEntries, ServerKeyCacheMaxEntries,
enablePrometheus,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -38,7 +40,7 @@ type InMemoryLRUCachePartition struct {
lru *lru.Cache lru *lru.Cache
} }
func NewInMemoryLRUCachePartition(name string, mutable bool, maxEntries int) (*InMemoryLRUCachePartition, error) { func NewInMemoryLRUCachePartition(name string, mutable bool, maxEntries int, enablePrometheus bool) (*InMemoryLRUCachePartition, error) {
var err error var err error
cache := InMemoryLRUCachePartition{ cache := InMemoryLRUCachePartition{
name: name, name: name,
@ -49,13 +51,15 @@ func NewInMemoryLRUCachePartition(name string, mutable bool, maxEntries int) (*I
if err != nil { if err != nil {
return nil, err return nil, err
} }
promauto.NewGaugeFunc(prometheus.GaugeOpts{ if enablePrometheus {
Namespace: "dendrite", promauto.NewGaugeFunc(prometheus.GaugeOpts{
Subsystem: "caching_in_memory_lru", Namespace: "dendrite",
Name: name, Subsystem: "caching_in_memory_lru",
}, func() float64 { Name: name,
return float64(cache.lru.Len()) }, func() float64 {
}) return float64(cache.lru.Len())
})
}
return &cache, nil return &cache, nil
} }

View file

@ -61,7 +61,7 @@ func (m *Monolith) AddAllPublicRoutes(publicMux *mux.Router) {
keyserver.AddPublicRoutes(publicMux, m.Config, m.DeviceDB, m.AccountDB) keyserver.AddPublicRoutes(publicMux, m.Config, m.DeviceDB, m.AccountDB)
federationapi.AddPublicRoutes( federationapi.AddPublicRoutes(
publicMux, m.Config, m.AccountDB, m.DeviceDB, m.FedClient, publicMux, m.Config, m.AccountDB, m.DeviceDB, m.FedClient,
m.KeyRing, m.RoomserverAPI, m.AppserviceAPI, m.FederationSenderAPI, m.ServerKeyAPI, m.RoomserverAPI, m.AppserviceAPI, m.FederationSenderAPI,
m.EDUInternalAPI, m.EDUInternalAPI,
) )
mediaapi.AddPublicRoutes(publicMux, m.Config, m.DeviceDB) mediaapi.AddPublicRoutes(publicMux, m.Config, m.DeviceDB)

View file

@ -22,6 +22,12 @@ type ServerKeyInternalAPI interface {
request *QueryPublicKeysRequest, request *QueryPublicKeysRequest,
response *QueryPublicKeysResponse, response *QueryPublicKeysResponse,
) error ) error
QueryLocalKeys(
ctx context.Context,
request *QueryLocalKeysRequest,
response *QueryLocalKeysResponse,
) error
} }
type QueryPublicKeysRequest struct { type QueryPublicKeysRequest struct {
@ -38,3 +44,10 @@ type InputPublicKeysRequest struct {
type InputPublicKeysResponse struct { type InputPublicKeysResponse struct {
} }
type QueryLocalKeysRequest struct {
}
type QueryLocalKeysResponse struct {
ServerKeys gomatrixserverlib.ServerKeys `json:"server_keys"`
}

View file

@ -2,9 +2,12 @@ package internal
import ( import (
"context" "context"
"crypto/ed25519"
"encoding/json"
"fmt" "fmt"
"time" "time"
"github.com/matrix-org/dendrite/internal/config"
"github.com/matrix-org/dendrite/serverkeyapi/api" "github.com/matrix-org/dendrite/serverkeyapi/api"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
) )
@ -12,10 +15,35 @@ import (
type ServerKeyAPI struct { type ServerKeyAPI struct {
api.ServerKeyInternalAPI api.ServerKeyInternalAPI
Cfg *config.Dendrite
OurKeyRing gomatrixserverlib.KeyRing OurKeyRing gomatrixserverlib.KeyRing
FedClient *gomatrixserverlib.FederationClient FedClient *gomatrixserverlib.FederationClient
} }
func (s *ServerKeyAPI) QueryLocalKeys(ctx context.Context, request *api.QueryLocalKeysRequest, response *api.QueryLocalKeysResponse) error {
response.ServerKeys.ServerName = s.Cfg.Matrix.ServerName
publicKey := s.Cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey)
response.ServerKeys.VerifyKeys = map[gomatrixserverlib.KeyID]gomatrixserverlib.VerifyKey{
s.Cfg.Matrix.KeyID: {
Key: gomatrixserverlib.Base64Bytes(publicKey),
},
}
response.ServerKeys.TLSFingerprints = s.Cfg.Matrix.TLSFingerPrints
response.ServerKeys.OldVerifyKeys = map[gomatrixserverlib.KeyID]gomatrixserverlib.OldVerifyKey{}
response.ServerKeys.ValidUntilTS = gomatrixserverlib.AsTimestamp(time.Now().Add(s.Cfg.Matrix.KeyValidityPeriod))
toSign, err := json.Marshal(response.ServerKeys.ServerKeyFields)
if err != nil {
return err
}
response.ServerKeys.Raw, err = gomatrixserverlib.SignJSON(
string(s.Cfg.Matrix.ServerName), s.Cfg.Matrix.KeyID, s.Cfg.Matrix.PrivateKey, toSign,
)
return err
}
func (s *ServerKeyAPI) KeyRing() *gomatrixserverlib.KeyRing { func (s *ServerKeyAPI) KeyRing() *gomatrixserverlib.KeyRing {
// Return a keyring that forces requests to be proxied through the // Return a keyring that forces requests to be proxied through the
// below functions. That way we can enforce things like validity // below functions. That way we can enforce things like validity
@ -50,16 +78,15 @@ func (s *ServerKeyAPI) FetchKeys(
// keys. These might come from a cache, depending on the database // keys. These might come from a cache, depending on the database
// implementation used. // implementation used.
if dbResults, err := s.OurKeyRing.KeyDatabase.FetchKeys(ctx, requests); err == nil { if dbResults, err := s.OurKeyRing.KeyDatabase.FetchKeys(ctx, requests); err == nil {
// We successfully got some keys. Add them to the results and // We successfully got some keys. Add them to the results.
// remove them from the request list.
for req, res := range dbResults { for req, res := range dbResults {
if !res.WasValidAt(now, true) {
// We appear to be past the key validity. Don't return this
// key with the results.
continue
}
results[req] = res results[req] = res
delete(requests, req) // 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 res.WasValidAt(now, true) {
delete(requests, req)
}
} }
} }
// For any key requests that we still have outstanding, next try to // For any key requests that we still have outstanding, next try to
@ -70,18 +97,37 @@ func (s *ServerKeyAPI) FetchKeys(
break break
} }
if fetcherResults, err := fetcher.FetchKeys(ctx, requests); err == nil { if fetcherResults, err := fetcher.FetchKeys(ctx, requests); err == nil {
// We successfully got some keys. Add them to the results and // Build a map of the results that we want to commit to the
// remove them from the request list. // 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 { for req, res := range fetcherResults {
if !res.WasValidAt(now, true) { if prev, ok := results[req]; ok {
// We appear to be past the key validity. Don't return this // We've already got a previous entry for this request
// key with the results. // so let's see if the newly retrieved one contains a more
continue // 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 results[req] = res
delete(requests, req) // If the key is valid right now then we can remove it from the
// request list as we won't need to re-fetch it.
if res.WasValidAt(now, true) {
delete(requests, req)
}
} }
if err = s.OurKeyRing.KeyDatabase.StoreKeys(ctx, fetcherResults); err != nil { // Store the keys from our store map.
if err = s.OurKeyRing.KeyDatabase.StoreKeys(ctx, storeResults); err != nil {
return nil, fmt.Errorf("server key API failed to store retrieved keys: %w", err) return nil, fmt.Errorf("server key API failed to store retrieved keys: %w", err)
} }
} }

View file

@ -16,6 +16,7 @@ import (
const ( const (
ServerKeyInputPublicKeyPath = "/serverkeyapi/inputPublicKey" ServerKeyInputPublicKeyPath = "/serverkeyapi/inputPublicKey"
ServerKeyQueryPublicKeyPath = "/serverkeyapi/queryPublicKey" ServerKeyQueryPublicKeyPath = "/serverkeyapi/queryPublicKey"
ServerKeyQueryLocalKeysPath = "/serverkeyapi/queryLocalKeys"
) )
// NewServerKeyClient creates a ServerKeyInternalAPI implemented by talking to a HTTP POST API. // NewServerKeyClient creates a ServerKeyInternalAPI implemented by talking to a HTTP POST API.
@ -130,3 +131,15 @@ func (h *httpServerKeyInternalAPI) QueryPublicKeys(
apiURL := h.serverKeyAPIURL + ServerKeyQueryPublicKeyPath apiURL := h.serverKeyAPIURL + ServerKeyQueryPublicKeyPath
return internalHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response) return internalHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
} }
func (h *httpServerKeyInternalAPI) QueryLocalKeys(
ctx context.Context,
request *api.QueryLocalKeysRequest,
response *api.QueryLocalKeysResponse,
) error {
span, ctx := opentracing.StartSpanFromContext(ctx, "QueryLocalKeys")
defer span.Finish()
apiURL := h.serverKeyAPIURL + ServerKeyQueryLocalKeysPath
return internalHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
}

View file

@ -12,6 +12,19 @@ import (
) )
func AddRoutes(s api.ServerKeyInternalAPI, internalAPIMux *mux.Router, cache caching.ServerKeyCache) { func AddRoutes(s api.ServerKeyInternalAPI, internalAPIMux *mux.Router, cache caching.ServerKeyCache) {
internalAPIMux.Handle(ServerKeyQueryLocalKeysPath,
internal.MakeInternalAPI("queryLocalKeys", func(req *http.Request) util.JSONResponse {
request := api.QueryLocalKeysRequest{}
response := api.QueryLocalKeysResponse{}
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error())
}
if err := s.QueryLocalKeys(req.Context(), &request, &response); err != nil {
return util.ErrorResponse(err)
}
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}),
)
internalAPIMux.Handle(ServerKeyQueryPublicKeyPath, internalAPIMux.Handle(ServerKeyQueryPublicKeyPath,
internal.MakeInternalAPI("queryPublicKeys", func(req *http.Request) util.JSONResponse { internal.MakeInternalAPI("queryPublicKeys", func(req *http.Request) util.JSONResponse {
request := api.QueryPublicKeysRequest{} request := api.QueryPublicKeysRequest{}

View file

@ -46,6 +46,7 @@ func NewInternalAPI(
} }
internalAPI := internal.ServerKeyAPI{ internalAPI := internal.ServerKeyAPI{
Cfg: cfg,
FedClient: fedClient, FedClient: fedClient,
OurKeyRing: gomatrixserverlib.KeyRing{ OurKeyRing: gomatrixserverlib.KeyRing{
KeyFetchers: []gomatrixserverlib.KeyFetcher{ KeyFetchers: []gomatrixserverlib.KeyFetcher{

View file

@ -8,10 +8,11 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os"
"reflect"
"testing" "testing"
"time" "time"
"github.com/matrix-org/dendrite/federationapi/routing"
"github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/internal/config" "github.com/matrix-org/dendrite/internal/config"
"github.com/matrix-org/dendrite/serverkeyapi/api" "github.com/matrix-org/dendrite/serverkeyapi/api"
@ -20,15 +21,19 @@ import (
type server struct { type server struct {
name gomatrixserverlib.ServerName name gomatrixserverlib.ServerName
validity time.Duration
config *config.Dendrite config *config.Dendrite
fedclient *gomatrixserverlib.FederationClient fedclient *gomatrixserverlib.FederationClient
cache *caching.Caches cache *caching.Caches
api api.ServerKeyInternalAPI api api.ServerKeyInternalAPI
} }
var serverA = &server{name: "a.com"} var (
var serverB = &server{name: "b.com"} serverKeyID = gomatrixserverlib.KeyID("ed25519:auto")
var serverC = &server{name: "c.com"} 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{ var servers = map[string]*server{
"a.com": serverA, "a.com": serverA,
@ -37,49 +42,59 @@ var servers = map[string]*server{
} }
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
caches, err := caching.NewInMemoryLRUCache() for _, s := range servers {
if err != nil {
panic("can't create cache: " + err.Error())
}
for _, s := range []*server{serverA, serverB, serverC} {
_, testPriv, err := ed25519.GenerateKey(nil) _, testPriv, err := ed25519.GenerateKey(nil)
if err != nil { if err != nil {
panic("can't generate identity key: " + err.Error()) panic("can't generate identity key: " + err.Error())
} }
s.cache, err = caching.NewInMemoryLRUCache(false)
if err != nil {
panic("can't create cache: " + err.Error())
}
s.config = &config.Dendrite{} s.config = &config.Dendrite{}
s.config.SetDefaults() s.config.SetDefaults()
s.config.Matrix.KeyValidityPeriod = s.validity
s.config.Matrix.ServerName = gomatrixserverlib.ServerName(s.name) s.config.Matrix.ServerName = gomatrixserverlib.ServerName(s.name)
s.config.Matrix.PrivateKey = testPriv s.config.Matrix.PrivateKey = testPriv
s.config.Matrix.KeyID = "ed25519:test" s.config.Matrix.KeyID = serverKeyID
s.config.Database.ServerKey = config.DataSource("file::memory:") s.config.Database.ServerKey = config.DataSource("file::memory:")
transport := &http.Transport{} transport := &http.Transport{}
transport.RegisterProtocol("matrix", &MockRoundTripper{}) transport.RegisterProtocol("matrix", &MockRoundTripper{})
s.fedclient = gomatrixserverlib.NewFederationClientWithTransport( s.fedclient = gomatrixserverlib.NewFederationClientWithTransport(
s.config.Matrix.ServerName, "ed25519:test", testPriv, transport, s.config.Matrix.ServerName, serverKeyID, testPriv, transport,
) )
s.cache = caches
s.api = NewInternalAPI(s.config, s.fedclient, s.cache) s.api = NewInternalAPI(s.config, s.fedclient, s.cache)
} }
os.Exit(m.Run())
} }
type MockRoundTripper struct{} type MockRoundTripper struct{}
func (m *MockRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) { func (m *MockRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) {
serv, ok := servers[req.Host] s, ok := servers[req.Host]
if !ok { if !ok {
return nil, fmt.Errorf("server not known: %s", req.Host) return nil, fmt.Errorf("server not known: %s", req.Host)
} }
keys := routing.LocalKeys(serv.config).JSON request := &api.QueryLocalKeysRequest{}
body, err := json.MarshalIndent(keys, "", " ") response := &api.QueryLocalKeysResponse{}
if err = s.api.QueryLocalKeys(context.Background(), request, response); err != nil {
return nil, err
}
body, err := json.MarshalIndent(response.ServerKeys, "", " ")
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmt.Println("Round-tripper says:", string(body))
res = &http.Response{ res = &http.Response{
StatusCode: 200, StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader(body)), Body: ioutil.NopCloser(bytes.NewReader(body)),
@ -87,18 +102,136 @@ func (m *MockRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err
return return
} }
func TestServerKeyAPIDirect(t *testing.T) { 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")
}
fmt.Printf("%s's key expires at %d\n", name, res[req].ValidUntilTS)
}
}
func TestServerARequestsServerBKey(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,
}
res, err := serverA.api.FetchKeys( res, err := serverA.api.FetchKeys(
context.Background(), context.Background(),
map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{ map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{
{ req: gomatrixserverlib.AsTimestamp(time.Now()),
ServerName: serverA.name, },
KeyID: "ed25519:test", )
}: gomatrixserverlib.AsTimestamp(time.Now()), 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.
*/
cres, ok := serverA.cache.GetServerKey(req)
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")
}
/*
Server A will then request Server B's key for an event that
happened two hours ago, which *should* pass since the key was
valid then too and it's already in the cache.
*/
_, err = serverA.api.FetchKeys(
context.Background(),
map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{
req: gomatrixserverlib.AsTimestamp(time.Now().Add(-time.Hour * 2)),
},
)
if err != nil {
t.Fatalf("server A failed to retrieve server B key: %s", err)
}
}
func TestServerARequestsServerCKey(t *testing.T) {
/*
Server A will request Server C's key for an event that came
in just now, but their validity period is an hour in the
past. This *should* fail since the key isn't valid now.
*/
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")
}
t.Log("server C's key expires at", res[req].ValidUntilTS.Time())
/*
Server A will then request Server C's key for an event that
happened two hours ago, which *should* pass since the key was
valid then.
*/
_, err = serverA.api.FetchKeys(
context.Background(),
map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp{
req: gomatrixserverlib.AsTimestamp(time.Now().Add(-time.Hour)),
}, },
) )
if err != nil { if err != nil {
t.Fatalf("serverKeyAPI.FetchKeys: %s", err) t.Fatalf("serverKeyAPI.FetchKeys: %s", err)
} }
t.Log(res)
} }

View file

@ -17,7 +17,6 @@ package postgres
import ( import (
"context" "context"
"time"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
@ -54,26 +53,28 @@ func NewDatabase(
} }
// Store our own keys so that we don't end up making HTTP requests to find our // Store our own keys so that we don't end up making HTTP requests to find our
// own keys // own keys
index := gomatrixserverlib.PublicKeyLookupRequest{ /*
ServerName: serverName, index := gomatrixserverlib.PublicKeyLookupRequest{
KeyID: serverKeyID, ServerName: serverName,
} KeyID: serverKeyID,
value := gomatrixserverlib.PublicKeyLookupResult{ }
VerifyKey: gomatrixserverlib.VerifyKey{ value := gomatrixserverlib.PublicKeyLookupResult{
Key: gomatrixserverlib.Base64Bytes(serverKey), VerifyKey: gomatrixserverlib.VerifyKey{
}, Key: gomatrixserverlib.Base64Bytes(serverKey),
ValidUntilTS: gomatrixserverlib.AsTimestamp(time.Now().Add(100 * 365 * 24 * time.Hour)), },
ExpiredTS: gomatrixserverlib.PublicKeyNotExpired, ValidUntilTS: gomatrixserverlib.AsTimestamp(time.Now().Add(100 * 365 * 24 * time.Hour)),
} ExpiredTS: gomatrixserverlib.PublicKeyNotExpired,
err = d.StoreKeys( }
context.Background(), err = d.StoreKeys(
map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{ context.Background(),
index: value, map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{
}, index: value,
) },
if err != nil { )
return nil, err if err != nil {
} return nil, err
}
*/
return d, nil return d, nil
} }

View file

@ -17,7 +17,6 @@ package sqlite3
import ( import (
"context" "context"
"time"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
@ -59,23 +58,25 @@ func NewDatabase(
} }
// Store our own keys so that we don't end up making HTTP requests to find our // Store our own keys so that we don't end up making HTTP requests to find our
// own keys // own keys
index := gomatrixserverlib.PublicKeyLookupRequest{ /*
ServerName: serverName, index := gomatrixserverlib.PublicKeyLookupRequest{
KeyID: serverKeyID, ServerName: serverName,
} KeyID: serverKeyID,
value := gomatrixserverlib.PublicKeyLookupResult{ }
VerifyKey: gomatrixserverlib.VerifyKey{ value := gomatrixserverlib.PublicKeyLookupResult{
Key: gomatrixserverlib.Base64Bytes(serverKey), VerifyKey: gomatrixserverlib.VerifyKey{
}, Key: gomatrixserverlib.Base64Bytes(serverKey),
ValidUntilTS: gomatrixserverlib.AsTimestamp(time.Now().Add(100 * 365 * 24 * time.Hour)), },
ExpiredTS: gomatrixserverlib.PublicKeyNotExpired, ValidUntilTS: gomatrixserverlib.AsTimestamp(time.Now().Add(100 * 365 * 24 * time.Hour)),
} ExpiredTS: gomatrixserverlib.PublicKeyNotExpired,
err = d.StoreKeys( }
context.Background(), err = d.StoreKeys(
map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{ context.Background(),
index: value, map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{
}, index: value,
) },
)
*/
if err != nil { if err != nil {
return nil, err return nil, err
} }