mirror of
https://github.com/matrix-org/dendrite.git
synced 2024-11-27 08:41:57 -06:00
7d77538ca4
This PR adds a block in the dendrite config for the services to bind to. The microservices should bind to the addresses in the bind block, and will be contacted at the address in the listen block. This fixes an issue with the microservices and kubernetes services.
725 lines
28 KiB
Go
725 lines
28 KiB
Go
// 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 (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
|
"github.com/matrix-org/gomatrixserverlib"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/crypto/ed25519"
|
|
yaml "gopkg.in/yaml.v2"
|
|
|
|
jaegerconfig "github.com/uber/jaeger-client-go/config"
|
|
jaegermetrics "github.com/uber/jaeger-lib/metrics"
|
|
)
|
|
|
|
// Version is the current version of the config format.
|
|
// This will change whenever we make breaking changes to the config format.
|
|
const Version = 0
|
|
|
|
// Dendrite contains all the config used by a dendrite process.
|
|
// Relative paths are resolved relative to the current working directory
|
|
type Dendrite struct {
|
|
// The version of the configuration file.
|
|
// If the version in a file doesn't match the current dendrite config
|
|
// version then we can give a clear error message telling the user
|
|
// to update their config file to the current version.
|
|
// The version of the file should only be different if there has
|
|
// been a breaking change to the config file format.
|
|
Version int `yaml:"version"`
|
|
|
|
// The configuration required for a matrix server.
|
|
Matrix struct {
|
|
// The name of the server. This is usually the domain name, e.g 'matrix.org', 'localhost'.
|
|
ServerName gomatrixserverlib.ServerName `yaml:"server_name"`
|
|
// Path to the private key which will be used to sign requests and events.
|
|
PrivateKeyPath Path `yaml:"private_key"`
|
|
// The private key which will be used to sign requests and events.
|
|
PrivateKey ed25519.PrivateKey `yaml:"-"`
|
|
// An arbitrary string used to uniquely identify the PrivateKey. Must start with the
|
|
// prefix "ed25519:".
|
|
KeyID gomatrixserverlib.KeyID `yaml:"-"`
|
|
// List of paths to X509 certificates used by the external federation listeners.
|
|
// These are used to calculate the TLS fingerprints to publish for this server.
|
|
// Other matrix servers talking to this server will expect the x509 certificate
|
|
// to match one of these certificates.
|
|
// The certificates should be in PEM format.
|
|
FederationCertificatePaths []Path `yaml:"federation_certificates"`
|
|
// A list of SHA256 TLS fingerprints for the X509 certificates used by the
|
|
// federation listener for this server.
|
|
TLSFingerPrints []gomatrixserverlib.TLSFingerprint `yaml:"-"`
|
|
// 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.
|
|
// Defaults to 24 hours.
|
|
KeyValidityPeriod time.Duration `yaml:"key_validity_period"`
|
|
// List of domains that the server will trust as identity servers to
|
|
// verify third-party identifiers.
|
|
// Defaults to an empty array.
|
|
TrustedIDServers []string `yaml:"trusted_third_party_id_servers"`
|
|
// If set, allows registration by anyone who also has the shared
|
|
// secret, even if registration is otherwise disabled.
|
|
RegistrationSharedSecret string `yaml:"registration_shared_secret"`
|
|
// This Home Server's ReCAPTCHA public key.
|
|
RecaptchaPublicKey string `yaml:"recaptcha_public_key"`
|
|
// This Home Server's ReCAPTCHA private key.
|
|
RecaptchaPrivateKey string `yaml:"recaptcha_private_key"`
|
|
// Boolean stating whether catpcha registration is enabled
|
|
// and required
|
|
RecaptchaEnabled bool `yaml:"enable_registration_captcha"`
|
|
// Secret used to bypass the captcha registration entirely
|
|
RecaptchaBypassSecret string `yaml:"captcha_bypass_secret"`
|
|
// HTTP API endpoint used to verify whether the captcha response
|
|
// was successful
|
|
RecaptchaSiteVerifyAPI string `yaml:"recaptcha_siteverify_api"`
|
|
// If set disables new users from registering (except via shared
|
|
// secrets)
|
|
RegistrationDisabled bool `yaml:"registration_disabled"`
|
|
} `yaml:"matrix"`
|
|
|
|
// The configuration specific to the media repostitory.
|
|
Media struct {
|
|
// The base path to where the media files will be stored. May be relative or absolute.
|
|
BasePath Path `yaml:"base_path"`
|
|
// The absolute base path to where media files will be stored.
|
|
AbsBasePath Path `yaml:"-"`
|
|
// The maximum file size in bytes that is allowed to be stored on this server.
|
|
// Note: if max_file_size_bytes is set to 0, the size is unlimited.
|
|
// Note: if max_file_size_bytes is not set, it will default to 10485760 (10MB)
|
|
MaxFileSizeBytes *FileSizeBytes `yaml:"max_file_size_bytes,omitempty"`
|
|
// Whether to dynamically generate thumbnails on-the-fly if the requested resolution is not already generated
|
|
DynamicThumbnails bool `yaml:"dynamic_thumbnails"`
|
|
// The maximum number of simultaneous thumbnail generators. default: 10
|
|
MaxThumbnailGenerators int `yaml:"max_thumbnail_generators"`
|
|
// A list of thumbnail sizes to be pre-generated for downloaded remote / uploaded content
|
|
ThumbnailSizes []ThumbnailSize `yaml:"thumbnail_sizes"`
|
|
} `yaml:"media"`
|
|
|
|
// The configuration for talking to kafka.
|
|
Kafka struct {
|
|
// A list of kafka addresses to connect to.
|
|
Addresses []string `yaml:"addresses"`
|
|
// Whether to use naffka instead of kafka.
|
|
// Naffka can only be used when running dendrite as a single monolithic server.
|
|
// Kafka can be used both with a monolithic server and when running the
|
|
// components as separate servers.
|
|
UseNaffka bool `yaml:"use_naffka,omitempty"`
|
|
// The names of the topics to use when reading and writing from kafka.
|
|
Topics struct {
|
|
// Topic for roomserver/api.OutputRoomEvent events.
|
|
OutputRoomEvent Topic `yaml:"output_room_event"`
|
|
// Topic for sending account data from client API to sync API
|
|
OutputClientData Topic `yaml:"output_client_data"`
|
|
// Topic for typingserver/api.OutputTypingEvent events.
|
|
OutputTypingEvent Topic `yaml:"output_typing_event"`
|
|
// Topic for user updates (profile, presence)
|
|
UserUpdates Topic `yaml:"user_updates"`
|
|
}
|
|
} `yaml:"kafka"`
|
|
|
|
// Postgres Config
|
|
Database struct {
|
|
// The Account database stores the login details and account information
|
|
// for local users. It is accessed by the ClientAPI.
|
|
Account DataSource `yaml:"account"`
|
|
// The Device database stores session information for the devices of logged
|
|
// in local users. It is accessed by the ClientAPI, the MediaAPI and the SyncAPI.
|
|
Device DataSource `yaml:"device"`
|
|
// The MediaAPI database stores information about files uploaded and downloaded
|
|
// by local users. It is only accessed by the MediaAPI.
|
|
MediaAPI DataSource `yaml:"media_api"`
|
|
// The ServerKey database caches the public keys of remote servers.
|
|
// It may be accessed by the FederationAPI, the ClientAPI, and the MediaAPI.
|
|
ServerKey DataSource `yaml:"server_key"`
|
|
// The SyncAPI stores information used by the SyncAPI server.
|
|
// It is only accessed by the SyncAPI server.
|
|
SyncAPI DataSource `yaml:"sync_api"`
|
|
// The RoomServer database stores information about matrix rooms.
|
|
// It is only accessed by the RoomServer.
|
|
RoomServer DataSource `yaml:"room_server"`
|
|
// The FederationSender database stores information used by the FederationSender
|
|
// It is only accessed by the FederationSender.
|
|
FederationSender DataSource `yaml:"federation_sender"`
|
|
// The AppServices database stores information used by the AppService component.
|
|
// It is only accessed by the AppService component.
|
|
AppService DataSource `yaml:"appservice"`
|
|
// The PublicRoomsAPI database stores information used to compute the public
|
|
// room directory. It is only accessed by the PublicRoomsAPI server.
|
|
PublicRoomsAPI DataSource `yaml:"public_rooms_api"`
|
|
// The Naffka database is used internally by the naffka library, if used.
|
|
Naffka DataSource `yaml:"naffka,omitempty"`
|
|
} `yaml:"database"`
|
|
|
|
// TURN Server Config
|
|
TURN struct {
|
|
// TODO Guest Support
|
|
// Whether or not guests can request TURN credentials
|
|
//AllowGuests bool `yaml:"turn_allow_guests"`
|
|
// How long the authorization should last
|
|
UserLifetime string `yaml:"turn_user_lifetime"`
|
|
// The list of TURN URIs to pass to clients
|
|
URIs []string `yaml:"turn_uris"`
|
|
|
|
// Authorization via Shared Secret
|
|
// The shared secret from coturn
|
|
SharedSecret string `yaml:"turn_shared_secret"`
|
|
|
|
// Authorization via Static Username & Password
|
|
// Hardcoded Username and Password
|
|
Username string `yaml:"turn_username"`
|
|
Password string `yaml:"turn_password"`
|
|
} `yaml:"turn"`
|
|
|
|
// The internal addresses the components will listen on.
|
|
// These should not be exposed externally as they expose metrics and debugging APIs.
|
|
// Falls back to addresses listed in Listen if not specified
|
|
Bind struct {
|
|
MediaAPI Address `yaml:"media_api"`
|
|
ClientAPI Address `yaml:"client_api"`
|
|
FederationAPI Address `yaml:"federation_api"`
|
|
AppServiceAPI Address `yaml:"appservice_api"`
|
|
SyncAPI Address `yaml:"sync_api"`
|
|
RoomServer Address `yaml:"room_server"`
|
|
FederationSender Address `yaml:"federation_sender"`
|
|
PublicRoomsAPI Address `yaml:"public_rooms_api"`
|
|
TypingServer Address `yaml:"typing_server"`
|
|
} `yaml:"bind"`
|
|
|
|
// The addresses for talking to other microservices.
|
|
Listen struct {
|
|
MediaAPI Address `yaml:"media_api"`
|
|
ClientAPI Address `yaml:"client_api"`
|
|
FederationAPI Address `yaml:"federation_api"`
|
|
AppServiceAPI Address `yaml:"appservice_api"`
|
|
SyncAPI Address `yaml:"sync_api"`
|
|
RoomServer Address `yaml:"room_server"`
|
|
FederationSender Address `yaml:"federation_sender"`
|
|
PublicRoomsAPI Address `yaml:"public_rooms_api"`
|
|
TypingServer Address `yaml:"typing_server"`
|
|
} `yaml:"listen"`
|
|
|
|
// The config for tracing the dendrite servers.
|
|
Tracing struct {
|
|
// The config for the jaeger opentracing reporter.
|
|
Jaeger jaegerconfig.Configuration `yaml:"jaeger"`
|
|
} `yaml:"tracing"`
|
|
|
|
// Application Services
|
|
// https://matrix.org/docs/spec/application_service/unstable.html
|
|
ApplicationServices struct {
|
|
// Configuration files for various application services
|
|
ConfigFiles []string `yaml:"config_files"`
|
|
} `yaml:"application_services"`
|
|
|
|
// The config for logging informations. Each hook will be added to logrus.
|
|
Logging []LogrusHook `yaml:"logging"`
|
|
|
|
// Any information derived from the configuration options for later use.
|
|
Derived struct {
|
|
Registration struct {
|
|
// Flows is a slice of flows, which represent one possible way that the client can authenticate a request.
|
|
// http://matrix.org/docs/spec/HEAD/client_server/r0.3.0.html#user-interactive-authentication-api
|
|
// As long as the generated flows only rely on config file options,
|
|
// we can generate them on startup and store them until needed
|
|
Flows []authtypes.Flow `json:"flows"`
|
|
|
|
// Params that need to be returned to the client during
|
|
// registration in order to complete registration stages.
|
|
Params map[string]interface{} `json:"params"`
|
|
}
|
|
|
|
// Application services parsed from their config files
|
|
// The paths of which were given above in the main config file
|
|
ApplicationServices []ApplicationService
|
|
|
|
// Meta-regexes compiled from all exclusive application service
|
|
// Regexes.
|
|
//
|
|
// When a user registers, we check that their username does not match any
|
|
// exclusive application service namespaces
|
|
ExclusiveApplicationServicesUsernameRegexp *regexp.Regexp
|
|
// When a user creates a room alias, we check that it isn't already
|
|
// reserved by an application service
|
|
ExclusiveApplicationServicesAliasRegexp *regexp.Regexp
|
|
// Note: An Exclusive Regex for room ID isn't necessary as we aren't blocking
|
|
// servers from creating RoomIDs in exclusive application service namespaces
|
|
} `yaml:"-"`
|
|
}
|
|
|
|
// A Path on the filesystem.
|
|
type Path string
|
|
|
|
// A DataSource for opening a postgresql database using lib/pq.
|
|
type DataSource string
|
|
|
|
// A Topic in kafka.
|
|
type Topic string
|
|
|
|
// An Address to listen on.
|
|
type Address string
|
|
|
|
// FileSizeBytes is a file size in bytes
|
|
type FileSizeBytes int64
|
|
|
|
// ThumbnailSize contains a single thumbnail size configuration
|
|
type ThumbnailSize struct {
|
|
// Maximum width of the thumbnail image
|
|
Width int `yaml:"width"`
|
|
// Maximum height of the thumbnail image
|
|
Height int `yaml:"height"`
|
|
// ResizeMethod is one of crop or scale.
|
|
// crop scales to fill the requested dimensions and crops the excess.
|
|
// scale scales to fit the requested dimensions and one dimension may be smaller than requested.
|
|
ResizeMethod string `yaml:"method,omitempty"`
|
|
}
|
|
|
|
// LogrusHook represents a single logrus hook. At this point, only parsing and
|
|
// verification of the proper values for type and level are done.
|
|
// Validity/integrity checks on the parameters are done when configuring logrus.
|
|
type LogrusHook struct {
|
|
// The type of hook, currently only "file" is supported.
|
|
Type string `yaml:"type"`
|
|
|
|
// The level of the logs to produce. Will output only this level and above.
|
|
Level string `yaml:"level"`
|
|
|
|
// The parameters for this hook.
|
|
Params map[string]interface{} `yaml:"params"`
|
|
}
|
|
|
|
// configErrors stores problems encountered when parsing a config file.
|
|
// It implements the error interface.
|
|
type configErrors []string
|
|
|
|
// Load a yaml config file for a server run as multiple processes.
|
|
// Checks the config to ensure that it is valid.
|
|
// The checks are different if the server is run as a monolithic process instead
|
|
// of being split into multiple components
|
|
func Load(configPath string) (*Dendrite, error) {
|
|
configData, err := ioutil.ReadFile(configPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
basePath, err := filepath.Abs(".")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Pass the current working directory and ioutil.ReadFile so that they can
|
|
// be mocked in the tests
|
|
monolithic := false
|
|
return loadConfig(basePath, configData, ioutil.ReadFile, monolithic)
|
|
}
|
|
|
|
// LoadMonolithic loads a yaml config file for a server run as a single monolith.
|
|
// Checks the config to ensure that it is valid.
|
|
// The checks are different if the server is run as a monolithic process instead
|
|
// of being split into multiple components
|
|
func LoadMonolithic(configPath string) (*Dendrite, error) {
|
|
configData, err := ioutil.ReadFile(configPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
basePath, err := filepath.Abs(".")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Pass the current working directory and ioutil.ReadFile so that they can
|
|
// be mocked in the tests
|
|
monolithic := true
|
|
return loadConfig(basePath, configData, ioutil.ReadFile, monolithic)
|
|
}
|
|
|
|
func loadConfig(
|
|
basePath string,
|
|
configData []byte,
|
|
readFile func(string) ([]byte, error),
|
|
monolithic bool,
|
|
) (*Dendrite, error) {
|
|
var config Dendrite
|
|
var err error
|
|
if err = yaml.Unmarshal(configData, &config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config.setDefaults()
|
|
|
|
if err = config.check(monolithic); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
privateKeyPath := absPath(basePath, config.Matrix.PrivateKeyPath)
|
|
privateKeyData, err := readFile(privateKeyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if config.Matrix.KeyID, config.Matrix.PrivateKey, err = readKeyPEM(privateKeyPath, privateKeyData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, certPath := range config.Matrix.FederationCertificatePaths {
|
|
absCertPath := absPath(basePath, certPath)
|
|
var pemData []byte
|
|
pemData, err = readFile(absCertPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fingerprint := fingerprintPEM(pemData)
|
|
if fingerprint == nil {
|
|
return nil, fmt.Errorf("no certificate PEM data in %q", absCertPath)
|
|
}
|
|
config.Matrix.TLSFingerPrints = append(config.Matrix.TLSFingerPrints, *fingerprint)
|
|
}
|
|
|
|
config.Media.AbsBasePath = Path(absPath(basePath, config.Media.BasePath))
|
|
|
|
// Generate data from config options
|
|
err = config.derive()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// derive generates data that is derived from various values provided in
|
|
// the config file.
|
|
func (config *Dendrite) derive() error {
|
|
// Determine registrations flows based off config values
|
|
|
|
config.Derived.Registration.Params = make(map[string]interface{})
|
|
|
|
// TODO: Add email auth type
|
|
// TODO: Add MSISDN auth type
|
|
|
|
if config.Matrix.RecaptchaEnabled {
|
|
config.Derived.Registration.Params[authtypes.LoginTypeRecaptcha] = map[string]string{"public_key": config.Matrix.RecaptchaPublicKey}
|
|
config.Derived.Registration.Flows = append(config.Derived.Registration.Flows,
|
|
authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeRecaptcha}})
|
|
} else {
|
|
config.Derived.Registration.Flows = append(config.Derived.Registration.Flows,
|
|
authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeDummy}})
|
|
}
|
|
|
|
// Load application service configuration files
|
|
if err := loadAppServices(config); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// setDefaults sets default config values if they are not explicitly set.
|
|
func (config *Dendrite) setDefaults() {
|
|
if config.Matrix.KeyValidityPeriod == 0 {
|
|
config.Matrix.KeyValidityPeriod = 24 * time.Hour
|
|
}
|
|
|
|
if config.Matrix.TrustedIDServers == nil {
|
|
config.Matrix.TrustedIDServers = []string{}
|
|
}
|
|
|
|
if config.Media.MaxThumbnailGenerators == 0 {
|
|
config.Media.MaxThumbnailGenerators = 10
|
|
}
|
|
|
|
if config.Media.MaxFileSizeBytes == nil {
|
|
defaultMaxFileSizeBytes := FileSizeBytes(10485760)
|
|
config.Media.MaxFileSizeBytes = &defaultMaxFileSizeBytes
|
|
}
|
|
}
|
|
|
|
// Error returns a string detailing how many errors were contained within a
|
|
// configErrors type.
|
|
func (errs configErrors) Error() string {
|
|
if len(errs) == 1 {
|
|
return errs[0]
|
|
}
|
|
return fmt.Sprintf(
|
|
"%s (and %d other problems)", errs[0], len(errs)-1,
|
|
)
|
|
}
|
|
|
|
// Add appends an error to the list of errors in this configErrors.
|
|
// It is a wrapper to the builtin append and hides pointers from
|
|
// the client code.
|
|
// This method is safe to use with an uninitialized configErrors because
|
|
// if it is nil, it will be properly allocated.
|
|
func (errs *configErrors) Add(str string) {
|
|
*errs = append(*errs, str)
|
|
}
|
|
|
|
// checkNotEmpty verifies the given value is not empty in the configuration.
|
|
// If it is, adds an error to the list.
|
|
func checkNotEmpty(configErrs *configErrors, key, value string) {
|
|
if value == "" {
|
|
configErrs.Add(fmt.Sprintf("missing config key %q", key))
|
|
}
|
|
}
|
|
|
|
// checkNotZero verifies the given value is not zero in the configuration.
|
|
// If it is, adds an error to the list.
|
|
func checkNotZero(configErrs *configErrors, key string, value int64) {
|
|
if value == 0 {
|
|
configErrs.Add(fmt.Sprintf("missing config key %q", key))
|
|
}
|
|
}
|
|
|
|
// checkPositive verifies the given value is positive (zero included)
|
|
// in the configuration. If it is not, adds an error to the list.
|
|
func checkPositive(configErrs *configErrors, key string, value int64) {
|
|
if value < 0 {
|
|
configErrs.Add(fmt.Sprintf("invalid value for config key %q: %d", key, value))
|
|
}
|
|
}
|
|
|
|
// checkTurn verifies the parameters turn.* are valid.
|
|
func (config *Dendrite) checkTurn(configErrs *configErrors) {
|
|
value := config.TURN.UserLifetime
|
|
if value != "" {
|
|
if _, err := time.ParseDuration(value); err != nil {
|
|
configErrs.Add(fmt.Sprintf("invalid duration for config key %q: %s", "turn.turn_user_lifetime", value))
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkMatrix verifies the parameters matrix.* are valid.
|
|
func (config *Dendrite) checkMatrix(configErrs *configErrors) {
|
|
checkNotEmpty(configErrs, "matrix.server_name", string(config.Matrix.ServerName))
|
|
checkNotEmpty(configErrs, "matrix.private_key", string(config.Matrix.PrivateKeyPath))
|
|
checkNotZero(configErrs, "matrix.federation_certificates", int64(len(config.Matrix.FederationCertificatePaths)))
|
|
if config.Matrix.RecaptchaEnabled {
|
|
checkNotEmpty(configErrs, "matrix.recaptcha_public_key", string(config.Matrix.RecaptchaPublicKey))
|
|
checkNotEmpty(configErrs, "matrix.recaptcha_private_key", string(config.Matrix.RecaptchaPrivateKey))
|
|
checkNotEmpty(configErrs, "matrix.recaptcha_siteverify_api", string(config.Matrix.RecaptchaSiteVerifyAPI))
|
|
}
|
|
}
|
|
|
|
// checkMedia verifies the parameters media.* are valid.
|
|
func (config *Dendrite) checkMedia(configErrs *configErrors) {
|
|
checkNotEmpty(configErrs, "media.base_path", string(config.Media.BasePath))
|
|
checkPositive(configErrs, "media.max_file_size_bytes", int64(*config.Media.MaxFileSizeBytes))
|
|
checkPositive(configErrs, "media.max_thumbnail_generators", int64(config.Media.MaxThumbnailGenerators))
|
|
|
|
for i, size := range config.Media.ThumbnailSizes {
|
|
checkPositive(configErrs, fmt.Sprintf("media.thumbnail_sizes[%d].width", i), int64(size.Width))
|
|
checkPositive(configErrs, fmt.Sprintf("media.thumbnail_sizes[%d].height", i), int64(size.Height))
|
|
}
|
|
}
|
|
|
|
// checkKafka verifies the parameters kafka.* and the related
|
|
// database.naffka are valid.
|
|
func (config *Dendrite) checkKafka(configErrs *configErrors, monolithic bool) {
|
|
|
|
if config.Kafka.UseNaffka {
|
|
if !monolithic {
|
|
configErrs.Add(fmt.Sprintf("naffka can only be used in a monolithic server"))
|
|
}
|
|
|
|
checkNotEmpty(configErrs, "database.naffka", string(config.Database.Naffka))
|
|
} else {
|
|
// If we aren't using naffka then we need to have at least one kafka
|
|
// server to talk to.
|
|
checkNotZero(configErrs, "kafka.addresses", int64(len(config.Kafka.Addresses)))
|
|
}
|
|
checkNotEmpty(configErrs, "kafka.topics.output_room_event", string(config.Kafka.Topics.OutputRoomEvent))
|
|
checkNotEmpty(configErrs, "kafka.topics.output_client_data", string(config.Kafka.Topics.OutputClientData))
|
|
checkNotEmpty(configErrs, "kafka.topics.output_typing_event", string(config.Kafka.Topics.OutputTypingEvent))
|
|
checkNotEmpty(configErrs, "kafka.topics.user_updates", string(config.Kafka.Topics.UserUpdates))
|
|
}
|
|
|
|
// checkDatabase verifies the parameters database.* are valid.
|
|
func (config *Dendrite) checkDatabase(configErrs *configErrors) {
|
|
checkNotEmpty(configErrs, "database.account", string(config.Database.Account))
|
|
checkNotEmpty(configErrs, "database.device", string(config.Database.Device))
|
|
checkNotEmpty(configErrs, "database.server_key", string(config.Database.ServerKey))
|
|
checkNotEmpty(configErrs, "database.media_api", string(config.Database.MediaAPI))
|
|
checkNotEmpty(configErrs, "database.sync_api", string(config.Database.SyncAPI))
|
|
checkNotEmpty(configErrs, "database.room_server", string(config.Database.RoomServer))
|
|
}
|
|
|
|
// checkListen verifies the parameters listen.* are valid.
|
|
func (config *Dendrite) checkListen(configErrs *configErrors) {
|
|
checkNotEmpty(configErrs, "listen.media_api", string(config.Listen.MediaAPI))
|
|
checkNotEmpty(configErrs, "listen.client_api", string(config.Listen.ClientAPI))
|
|
checkNotEmpty(configErrs, "listen.federation_api", string(config.Listen.FederationAPI))
|
|
checkNotEmpty(configErrs, "listen.sync_api", string(config.Listen.SyncAPI))
|
|
checkNotEmpty(configErrs, "listen.room_server", string(config.Listen.RoomServer))
|
|
checkNotEmpty(configErrs, "listen.typing_server", string(config.Listen.TypingServer))
|
|
}
|
|
|
|
// checkLogging verifies the parameters logging.* are valid.
|
|
func (config *Dendrite) checkLogging(configErrs *configErrors) {
|
|
for _, logrusHook := range config.Logging {
|
|
checkNotEmpty(configErrs, "logging.type", string(logrusHook.Type))
|
|
checkNotEmpty(configErrs, "logging.level", string(logrusHook.Level))
|
|
}
|
|
}
|
|
|
|
// check returns an error type containing all errors found within the config
|
|
// file.
|
|
func (config *Dendrite) check(monolithic bool) error {
|
|
var configErrs configErrors
|
|
|
|
if config.Version != Version {
|
|
configErrs.Add(fmt.Sprintf(
|
|
"unknown config version %q, expected %q", config.Version, Version,
|
|
))
|
|
return configErrs
|
|
}
|
|
|
|
config.checkMatrix(&configErrs)
|
|
config.checkMedia(&configErrs)
|
|
config.checkTurn(&configErrs)
|
|
config.checkKafka(&configErrs, monolithic)
|
|
config.checkDatabase(&configErrs)
|
|
config.checkLogging(&configErrs)
|
|
|
|
if !monolithic {
|
|
config.checkListen(&configErrs)
|
|
}
|
|
|
|
// Due to how Golang manages its interface types, this condition is not redundant.
|
|
// In order to get the proper behaviour, it is necessary to return an explicit nil
|
|
// and not a nil configErrors.
|
|
// This is because the following equalities hold:
|
|
// error(nil) == nil
|
|
// error(configErrors(nil)) != nil
|
|
if configErrs != nil {
|
|
return configErrs
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// absPath returns the absolute path for a given relative or absolute path.
|
|
func absPath(dir string, path Path) string {
|
|
if filepath.IsAbs(string(path)) {
|
|
// filepath.Join cleans the path so we should clean the absolute paths as well for consistency.
|
|
return filepath.Clean(string(path))
|
|
}
|
|
return filepath.Join(dir, string(path))
|
|
}
|
|
|
|
func readKeyPEM(path string, data []byte) (gomatrixserverlib.KeyID, ed25519.PrivateKey, error) {
|
|
for {
|
|
var keyBlock *pem.Block
|
|
keyBlock, data = pem.Decode(data)
|
|
if data == nil {
|
|
return "", nil, fmt.Errorf("no matrix private key PEM data in %q", path)
|
|
}
|
|
if keyBlock == nil {
|
|
return "", nil, fmt.Errorf("keyBlock is nil %q", path)
|
|
}
|
|
if keyBlock.Type == "MATRIX PRIVATE KEY" {
|
|
keyID := keyBlock.Headers["Key-ID"]
|
|
if keyID == "" {
|
|
return "", nil, fmt.Errorf("missing key ID in PEM data in %q", path)
|
|
}
|
|
if !strings.HasPrefix(keyID, "ed25519:") {
|
|
return "", nil, fmt.Errorf("key ID %q doesn't start with \"ed25519:\" in %q", keyID, path)
|
|
}
|
|
_, privKey, err := ed25519.GenerateKey(bytes.NewReader(keyBlock.Bytes))
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
return gomatrixserverlib.KeyID(keyID), privKey, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func fingerprintPEM(data []byte) *gomatrixserverlib.TLSFingerprint {
|
|
for {
|
|
var certDERBlock *pem.Block
|
|
certDERBlock, data = pem.Decode(data)
|
|
if data == nil {
|
|
return nil
|
|
}
|
|
if certDERBlock.Type == "CERTIFICATE" {
|
|
digest := sha256.Sum256(certDERBlock.Bytes)
|
|
return &gomatrixserverlib.TLSFingerprint{SHA256: digest[:]}
|
|
}
|
|
}
|
|
}
|
|
|
|
// AppServiceURL returns a HTTP URL for where the appservice component is listening.
|
|
func (config *Dendrite) AppServiceURL() string {
|
|
// Hard code the roomserver 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 "http://" + string(config.Listen.AppServiceAPI)
|
|
}
|
|
|
|
// RoomServerURL returns an HTTP URL for where the roomserver is listening.
|
|
func (config *Dendrite) RoomServerURL() string {
|
|
// Hard code the roomserver 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 "http://" + string(config.Listen.RoomServer)
|
|
}
|
|
|
|
// TypingServerURL returns an HTTP URL for where the typing server is listening.
|
|
func (config *Dendrite) TypingServerURL() string {
|
|
// Hard code the typing 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 "http://" + string(config.Listen.TypingServer)
|
|
}
|
|
|
|
// FederationSenderURL returns an HTTP URL for where the federation sender is listening.
|
|
func (config *Dendrite) FederationSenderURL() string {
|
|
// Hard code the typing 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 "http://" + string(config.Listen.FederationSender)
|
|
}
|
|
|
|
// SetupTracing configures the opentracing using the supplied configuration.
|
|
func (config *Dendrite) SetupTracing(serviceName string) (closer io.Closer, err error) {
|
|
return config.Tracing.Jaeger.InitGlobalTracer(
|
|
serviceName,
|
|
jaegerconfig.Logger(logrusLogger{logrus.StandardLogger()}),
|
|
jaegerconfig.Metrics(jaegermetrics.NullFactory),
|
|
)
|
|
}
|
|
|
|
// logrusLogger is a small wrapper that implements jaeger.Logger using logrus.
|
|
type logrusLogger struct {
|
|
l *logrus.Logger
|
|
}
|
|
|
|
func (l logrusLogger) Error(msg string) {
|
|
l.l.Error(msg)
|
|
}
|
|
|
|
func (l logrusLogger) Infof(msg string, args ...interface{}) {
|
|
l.l.Infof(msg, args...)
|
|
}
|