diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go index d6d6f36b6..bf1bd9f68 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go @@ -19,13 +19,12 @@ import ( "os" "strings" - "golang.org/x/crypto/ed25519" - "github.com/matrix-org/dendrite/clientapi/config" "github.com/matrix-org/dendrite/clientapi/producers" "github.com/matrix-org/dendrite/clientapi/routing" "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/gomatrixserverlib" log "github.com/Sirupsen/logrus" ) @@ -36,6 +35,8 @@ var ( logDir = os.Getenv("LOG_DIR") roomserverURL = os.Getenv("ROOMSERVER_URL") clientAPIOutputTopic = os.Getenv("CLIENTAPI_OUTPUT_TOPIC") + serverName = gomatrixserverlib.ServerName(os.Getenv("SERVER_NAME")) + serverKey = os.Getenv("SERVER_KEY") ) func main() { @@ -53,23 +54,23 @@ func main() { if clientAPIOutputTopic == "" { log.Panic("No CLIENTAPI_OUTPUT_TOPIC environment variable found. This should match the roomserver input topic.") } - - // TODO: Rather than generating a new key on every startup, we should be - // reading a PEM formatted file instead. - _, privKey, err := ed25519.GenerateKey(nil) - if err != nil { - log.Panicf("Failed to generate private key: %s", err) + if serverName == "" { + serverName = "localhost" } cfg := config.ClientAPI{ - ServerName: "localhost", - KeyID: "ed25519:something", - PrivateKey: privKey, + ServerName: serverName, KafkaProducerURIs: kafkaURIs, ClientAPIOutputTopic: clientAPIOutputTopic, RoomserverURL: roomserverURL, } + var err error + cfg.KeyID, cfg.PrivateKey, err = common.ReadKey(serverKey) + if err != nil { + log.Panicf("Failed to load private key: %s", err) + } + log.Info("Starting clientapi") roomserverProducer, err := producers.NewRoomserverProducer(cfg.KafkaProducerURIs, cfg.ClientAPIOutputTopic) diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-federation-api-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-federation-api-server/main.go new file mode 100644 index 000000000..220a3d9bc --- /dev/null +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-federation-api-server/main.go @@ -0,0 +1,61 @@ +// 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 main + +import ( + "net/http" + "os" + "time" + + "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/federationapi/config" + "github.com/matrix-org/dendrite/federationapi/routing" + "github.com/matrix-org/gomatrixserverlib" + + log "github.com/Sirupsen/logrus" +) + +var ( + bindAddr = os.Getenv("BIND_ADDRESS") + logDir = os.Getenv("LOG_DIR") + serverName = gomatrixserverlib.ServerName(os.Getenv("SERVER_NAME")) + serverKey = os.Getenv("SERVER_KEY") +) + +func main() { + common.SetupLogging(logDir) + if bindAddr == "" { + log.Panic("No BIND_ADDRESS environment variable found.") + } + + if serverName == "" { + serverName = "localhost" + } + + cfg := config.FederationAPI{ + ServerName: serverName, + // TODO: make the validity period configurable. + ValidityPeriod: 24 * time.Hour, + } + + var err error + cfg.KeyID, cfg.PrivateKey, err = common.ReadKey(serverKey) + if err != nil { + log.Panicf("Failed to load private key: %s", err) + } + + routing.Setup(http.DefaultServeMux, cfg) + log.Fatal(http.ListenAndServe(bindAddr, nil)) +} diff --git a/src/github.com/matrix-org/dendrite/common/readkey.go b/src/github.com/matrix-org/dendrite/common/readkey.go new file mode 100644 index 000000000..7c7d95dfd --- /dev/null +++ b/src/github.com/matrix-org/dendrite/common/readkey.go @@ -0,0 +1,58 @@ +// 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 common + +import ( + "bytes" + "encoding/base64" + "fmt" + "github.com/matrix-org/gomatrixserverlib" + "golang.org/x/crypto/ed25519" + "io" + "strings" +) + +// ReadKey reads a server's private ed25519 key. +// If the key is the empty string then a random key is generated. +// Otherwise the key is the key ID and the base64 encoded private key +// separated by a single space character. +// E.g "ed25519:abcd ABCDEFGHIJKLMNOPabcdefghijklmnop01234567890" +func ReadKey(key string) (gomatrixserverlib.KeyID, ed25519.PrivateKey, error) { + var keyID gomatrixserverlib.KeyID + var seed io.Reader + if key == "" { + // TODO: We should fail if we don't have a private key rather than + // generating a throw away key. + keyID = gomatrixserverlib.KeyID("ed25519:something") + } else { + // TODO: We should be reading this from a PEM formatted file instead of + // reading from the environment directly. + parts := strings.SplitN(key, " ", 2) + keyID = gomatrixserverlib.KeyID(parts[0]) + if len(parts) != 2 { + return "", nil, fmt.Errorf("Invalid server key: %q", key) + } + seedBytes, err := base64.RawStdEncoding.DecodeString(parts[1]) + if err != nil { + return "", nil, err + } + seed = bytes.NewReader(seedBytes) + } + _, privKey, err := ed25519.GenerateKey(seed) + if err != nil { + return "", nil, err + } + return keyID, privKey, nil +} diff --git a/src/github.com/matrix-org/dendrite/federationapi/config/config.go b/src/github.com/matrix-org/dendrite/federationapi/config/config.go new file mode 100644 index 000000000..0e2012ca6 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/federationapi/config/config.go @@ -0,0 +1,39 @@ +// 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 config + +import ( + "github.com/matrix-org/gomatrixserverlib" + "golang.org/x/crypto/ed25519" + "time" +) + +// FederationAPI contains the config information necessary to spin up a federationapi process. +type FederationAPI struct { + // The name of the server. This is usually the domain name, e.g 'matrix.org', 'localhost'. + ServerName gomatrixserverlib.ServerName + // The private key which will be used to sign requests. + PrivateKey ed25519.PrivateKey + // An arbitrary string used to uniquely identify the PrivateKey. Must start with the + // prefix "ed25519:". + KeyID gomatrixserverlib.KeyID + // A list of SHA256 TLS fingerprints for this server. + TLSFingerPrints []gomatrixserverlib.TLSFingerprint + // How long a remote server can cache our server key for before requesting it again. + // Increasing this number will reduce the number of requests made by remote servers + // for our key, but increases the period a compromised key will be considered valid + // by remote servers. + ValidityPeriod time.Duration +} diff --git a/src/github.com/matrix-org/dendrite/federationapi/readers/keys.go b/src/github.com/matrix-org/dendrite/federationapi/readers/keys.go new file mode 100644 index 000000000..edae0c989 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/federationapi/readers/keys.go @@ -0,0 +1,66 @@ +// 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 readers + +import ( + "encoding/json" + "github.com/matrix-org/dendrite/federationapi/config" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" + "golang.org/x/crypto/ed25519" + "net/http" + "time" +) + +// LocalKeys returns the local keys for the server. +// See https://matrix.org/docs/spec/server_server/unstable.html#publishing-keys +func LocalKeys(req *http.Request, cfg config.FederationAPI) util.JSONResponse { + keys, err := localKeys(cfg, time.Now().Add(cfg.ValidityPeriod)) + if err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: 200, JSON: keys} +} + +func localKeys(cfg config.FederationAPI, validUntil time.Time) (*gomatrixserverlib.ServerKeys, error) { + var keys gomatrixserverlib.ServerKeys + + keys.ServerName = cfg.ServerName + keys.FromServer = cfg.ServerName + + publicKey := cfg.PrivateKey.Public().(ed25519.PublicKey) + + keys.VerifyKeys = map[gomatrixserverlib.KeyID]gomatrixserverlib.VerifyKey{ + cfg.KeyID: gomatrixserverlib.VerifyKey{ + gomatrixserverlib.Base64String(publicKey), + }, + } + + keys.TLSFingerprints = cfg.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.ServerName), cfg.KeyID, cfg.PrivateKey, toSign) + if err != nil { + return nil, err + } + + return &keys, nil +} diff --git a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go new file mode 100644 index 000000000..41486de29 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go @@ -0,0 +1,48 @@ +// 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 ( + "github.com/gorilla/mux" + "github.com/matrix-org/dendrite/federationapi/config" + "github.com/matrix-org/dendrite/federationapi/readers" + "github.com/matrix-org/util" + "github.com/prometheus/client_golang/prometheus" + "net/http" +) + +const ( + pathPrefixV2Keys = "/_matrix/keys/v2" +) + +// Setup registers HTTP handlers with the given ServeMux. +func Setup(servMux *http.ServeMux, cfg config.FederationAPI) { + apiMux := mux.NewRouter() + v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter() + + v2keysmux.Handle("/server/", + makeAPI("localkeys", func(req *http.Request) util.JSONResponse { + return readers.LocalKeys(req, cfg) + }), + ) + + servMux.Handle("/metrics", prometheus.Handler()) + servMux.Handle("/api/", http.StripPrefix("/api", apiMux)) +} + +func makeAPI(metricsName string, f func(*http.Request) util.JSONResponse) http.Handler { + h := util.NewJSONRequestHandler(f) + return prometheus.InstrumentHandler(metricsName, util.MakeJSONAPI(h)) +}