diff --git a/cmd/create-account/main.go b/cmd/create-account/main.go index bba2d55d6..22732c518 100644 --- a/cmd/create-account/main.go +++ b/cmd/create-account/main.go @@ -24,6 +24,7 @@ import ( "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/userapi/storage/accounts" "github.com/sirupsen/logrus" + "golang.org/x/crypto/bcrypt" ) const usage = `Usage: %s @@ -57,7 +58,7 @@ func main() { accountDB, err := accounts.NewDatabase(&config.DatabaseOptions{ ConnectionString: cfg.UserAPI.AccountDatabase.ConnectionString, - }, cfg.Global.ServerName) + }, cfg.Global.ServerName, bcrypt.DefaultCost) if err != nil { logrus.Fatalln("Failed to connect to the database:", err.Error()) } diff --git a/cmd/generate-config/main.go b/cmd/generate-config/main.go index 9ef0f0b41..bd70cbc5d 100644 --- a/cmd/generate-config/main.go +++ b/cmd/generate-config/main.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/matrix-org/dendrite/setup/config" + "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v2" ) @@ -68,6 +69,7 @@ func main() { cfg.Logging[0].Level = "trace" // don't hit matrix.org when running tests!!! cfg.SigningKeyServer.KeyPerspectives = config.KeyPerspectives{} + cfg.UserAPI.BCryptCost = bcrypt.MinCost } j, err := yaml.Marshal(cfg) diff --git a/dendrite-config.yaml b/dendrite-config.yaml index bf604c9d6..13564590d 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -240,7 +240,7 @@ media_api: listen: http://[::]:8074 database: connection_string: file:mediaapi.db - max_open_conns: 10 + max_open_conns: 5 max_idle_conns: 2 conn_max_lifetime: -1 @@ -278,7 +278,7 @@ mscs: mscs: [] database: connection_string: file:mscs.db - max_open_conns: 10 + max_open_conns: 5 max_idle_conns: 2 conn_max_lifetime: -1 @@ -340,6 +340,13 @@ sync_api: # Configuration for the User API. user_api: + # The cost when hashing passwords on registration/login. Default: 10. Min: 4, Max: 31 + # See https://pkg.go.dev/golang.org/x/crypto/bcrypt for more information. + # Setting this lower makes registration/login consume less CPU resources at the cost of security + # should the database be compromised. Setting this higher makes registration/login consume more + # CPU resources but makes it harder to brute force password hashes. + # This value can be low if performing tests or on embedded Dendrite instances (e.g WASM builds) + # bcrypt_cost: 10 internal_api: listen: http://localhost:7781 connect: http://localhost:7781 diff --git a/setup/base.go b/setup/base.go index e9aa2a45e..4c50a6be6 100644 --- a/setup/base.go +++ b/setup/base.go @@ -263,7 +263,7 @@ func (b *BaseDendrite) KeyServerHTTPClient() keyserverAPI.KeyInternalAPI { // CreateAccountsDB creates a new instance of the accounts database. Should only // be called once per component. func (b *BaseDendrite) CreateAccountsDB() accounts.Database { - db, err := accounts.NewDatabase(&b.Cfg.UserAPI.AccountDatabase, b.Cfg.Global.ServerName) + db, err := accounts.NewDatabase(&b.Cfg.UserAPI.AccountDatabase, b.Cfg.Global.ServerName, b.Cfg.UserAPI.BCryptCost) if err != nil { logrus.WithError(err).Panicf("failed to connect to accounts db") } diff --git a/setup/config/config_appservice.go b/setup/config/config_appservice.go index 724f035f4..57cc7be55 100644 --- a/setup/config/config_appservice.go +++ b/setup/config/config_appservice.go @@ -43,7 +43,7 @@ type AppServiceAPI struct { func (c *AppServiceAPI) Defaults() { c.InternalAPI.Listen = "http://localhost:7777" c.InternalAPI.Connect = "http://localhost:7777" - c.Database.Defaults() + c.Database.Defaults(10) c.Database.ConnectionString = "file:appservice.db" } diff --git a/setup/config/config_federationsender.go b/setup/config/config_federationsender.go index 84f5b6f40..67ee63565 100644 --- a/setup/config/config_federationsender.go +++ b/setup/config/config_federationsender.go @@ -25,7 +25,7 @@ type FederationSender struct { func (c *FederationSender) Defaults() { c.InternalAPI.Listen = "http://localhost:7775" c.InternalAPI.Connect = "http://localhost:7775" - c.Database.Defaults() + c.Database.Defaults(10) c.Database.ConnectionString = "file:federationsender.db" c.FederationMaxRetries = 16 diff --git a/setup/config/config_global.go b/setup/config/config_global.go index d4b068dbe..4b5297ff6 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -122,8 +122,8 @@ type DatabaseOptions struct { ConnMaxLifetimeSeconds int `yaml:"conn_max_lifetime"` } -func (c *DatabaseOptions) Defaults() { - c.MaxOpenConnections = 100 +func (c *DatabaseOptions) Defaults(conns int) { + c.MaxOpenConnections = conns c.MaxIdleConnections = 2 c.ConnMaxLifetimeSeconds = -1 } diff --git a/setup/config/config_kafka.go b/setup/config/config_kafka.go index aa91e5589..361914287 100644 --- a/setup/config/config_kafka.go +++ b/setup/config/config_kafka.go @@ -36,7 +36,7 @@ func (k *Kafka) TopicFor(name string) string { func (c *Kafka) Defaults() { c.UseNaffka = true - c.Database.Defaults() + c.Database.Defaults(10) c.Addresses = []string{"localhost:2181"} c.Database.ConnectionString = DataSource("file:naffka.db") c.TopicPrefix = "Dendrite" diff --git a/setup/config/config_keyserver.go b/setup/config/config_keyserver.go index 891623008..62a30dbb9 100644 --- a/setup/config/config_keyserver.go +++ b/setup/config/config_keyserver.go @@ -11,7 +11,7 @@ type KeyServer struct { func (c *KeyServer) Defaults() { c.InternalAPI.Listen = "http://localhost:7779" c.InternalAPI.Connect = "http://localhost:7779" - c.Database.Defaults() + c.Database.Defaults(10) c.Database.ConnectionString = "file:keyserver.db" } diff --git a/setup/config/config_mediaapi.go b/setup/config/config_mediaapi.go index a9425b7be..660a508d5 100644 --- a/setup/config/config_mediaapi.go +++ b/setup/config/config_mediaapi.go @@ -39,7 +39,7 @@ func (c *MediaAPI) Defaults() { c.InternalAPI.Listen = "http://localhost:7774" c.InternalAPI.Connect = "http://localhost:7774" c.ExternalAPI.Listen = "http://[::]:8074" - c.Database.Defaults() + c.Database.Defaults(5) c.Database.ConnectionString = "file:mediaapi.db" defaultMaxFileSizeBytes := FileSizeBytes(10485760) diff --git a/setup/config/config_mscs.go b/setup/config/config_mscs.go index 764273ecc..a94e5dc56 100644 --- a/setup/config/config_mscs.go +++ b/setup/config/config_mscs.go @@ -14,7 +14,7 @@ type MSCs struct { } func (c *MSCs) Defaults() { - c.Database.Defaults() + c.Database.Defaults(5) c.Database.ConnectionString = "file:mscs.db" } diff --git a/setup/config/config_roomserver.go b/setup/config/config_roomserver.go index 2a1fc38b3..ffb9b5f9d 100644 --- a/setup/config/config_roomserver.go +++ b/setup/config/config_roomserver.go @@ -11,7 +11,7 @@ type RoomServer struct { func (c *RoomServer) Defaults() { c.InternalAPI.Listen = "http://localhost:7770" c.InternalAPI.Connect = "http://localhost:7770" - c.Database.Defaults() + c.Database.Defaults(10) c.Database.ConnectionString = "file:roomserver.db" } diff --git a/setup/config/config_signingkeyserver.go b/setup/config/config_signingkeyserver.go index 51aca38bd..c192d1fc0 100644 --- a/setup/config/config_signingkeyserver.go +++ b/setup/config/config_signingkeyserver.go @@ -22,7 +22,7 @@ type SigningKeyServer struct { func (c *SigningKeyServer) Defaults() { c.InternalAPI.Listen = "http://localhost:7780" c.InternalAPI.Connect = "http://localhost:7780" - c.Database.Defaults() + c.Database.Defaults(10) c.Database.ConnectionString = "file:signingkeyserver.db" } diff --git a/setup/config/config_syncapi.go b/setup/config/config_syncapi.go index fc08f7380..4b9bfb161 100644 --- a/setup/config/config_syncapi.go +++ b/setup/config/config_syncapi.go @@ -15,7 +15,7 @@ func (c *SyncAPI) Defaults() { c.InternalAPI.Listen = "http://localhost:7773" c.InternalAPI.Connect = "http://localhost:7773" c.ExternalAPI.Listen = "http://localhost:8073" - c.Database.Defaults() + c.Database.Defaults(10) c.Database.ConnectionString = "file:syncapi.db" } diff --git a/setup/config/config_userapi.go b/setup/config/config_userapi.go index 2cbd8a450..e69123842 100644 --- a/setup/config/config_userapi.go +++ b/setup/config/config_userapi.go @@ -1,10 +1,15 @@ package config +import "golang.org/x/crypto/bcrypt" + type UserAPI struct { Matrix *Global `yaml:"-"` InternalAPI InternalAPIOptions `yaml:"internal_api"` + // The cost when hashing passwords. + BCryptCost int `yaml:"bcrypt_cost"` + // The Account database stores the login details and account information // for local users. It is accessed by the UserAPI. AccountDatabase DatabaseOptions `yaml:"account_database"` @@ -16,10 +21,11 @@ type UserAPI struct { func (c *UserAPI) Defaults() { c.InternalAPI.Listen = "http://localhost:7781" c.InternalAPI.Connect = "http://localhost:7781" - c.AccountDatabase.Defaults() - c.DeviceDatabase.Defaults() + c.AccountDatabase.Defaults(10) + c.DeviceDatabase.Defaults(10) c.AccountDatabase.ConnectionString = "file:userapi_accounts.db" c.DeviceDatabase.ConnectionString = "file:userapi_devices.db" + c.BCryptCost = bcrypt.DefaultCost } func (c *UserAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { diff --git a/sytest-whitelist b/sytest-whitelist index 7ccbaad14..ed02fdecb 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -516,3 +516,4 @@ AS can create a user with inhibit_login AS can set avatar for ghosted users AS can set displayname for ghosted users Ghost user must register before joining room +Inviting an AS-hosted user asks the AS server diff --git a/userapi/storage/accounts/postgres/storage.go b/userapi/storage/accounts/postgres/storage.go index e6adbfd83..3933fe5bd 100644 --- a/userapi/storage/accounts/postgres/storage.go +++ b/userapi/storage/accounts/postgres/storage.go @@ -44,10 +44,11 @@ type Database struct { accountDatas accountDataStatements threepids threepidStatements serverName gomatrixserverlib.ServerName + bcryptCost int } // NewDatabase creates a new accounts and profiles database -func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName) (*Database, error) { +func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (*Database, error) { db, err := sqlutil.Open(dbProperties) if err != nil { return nil, err @@ -56,6 +57,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver serverName: serverName, db: db, writer: sqlutil.NewDummyWriter(), + bcryptCost: bcryptCost, } // Create tables before executing migrations so we don't fail if the table is missing, @@ -131,7 +133,7 @@ func (d *Database) SetDisplayName( func (d *Database) SetPassword( ctx context.Context, localpart, plaintextPassword string, ) error { - hash, err := hashPassword(plaintextPassword) + hash, err := d.hashPassword(plaintextPassword) if err != nil { return err } @@ -175,7 +177,7 @@ func (d *Database) createAccount( // Generate a password hash if this is not a password-less user hash := "" if plaintextPassword != "" { - hash, err = hashPassword(plaintextPassword) + hash, err = d.hashPassword(plaintextPassword) if err != nil { return nil, err } @@ -246,8 +248,8 @@ func (d *Database) GetNewNumericLocalpart( return d.accounts.selectNewNumericLocalpart(ctx, nil) } -func hashPassword(plaintext string) (hash string, err error) { - hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.DefaultCost) +func (d *Database) hashPassword(plaintext string) (hash string, err error) { + hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), d.bcryptCost) return string(hashBytes), err } diff --git a/userapi/storage/accounts/sqlite3/storage.go b/userapi/storage/accounts/sqlite3/storage.go index 747be34f6..07cc68b35 100644 --- a/userapi/storage/accounts/sqlite3/storage.go +++ b/userapi/storage/accounts/sqlite3/storage.go @@ -42,6 +42,7 @@ type Database struct { accountDatas accountDataStatements threepids threepidStatements serverName gomatrixserverlib.ServerName + bcryptCost int accountsMu sync.Mutex profilesMu sync.Mutex @@ -50,7 +51,7 @@ type Database struct { } // NewDatabase creates a new accounts and profiles database -func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName) (*Database, error) { +func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (*Database, error) { db, err := sqlutil.Open(dbProperties) if err != nil { return nil, err @@ -59,6 +60,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver serverName: serverName, db: db, writer: sqlutil.NewExclusiveWriter(), + bcryptCost: bcryptCost, } // Create tables before executing migrations so we don't fail if the table is missing, @@ -143,7 +145,7 @@ func (d *Database) SetDisplayName( func (d *Database) SetPassword( ctx context.Context, localpart, plaintextPassword string, ) error { - hash, err := hashPassword(plaintextPassword) + hash, err := d.hashPassword(plaintextPassword) if err != nil { return err } @@ -208,7 +210,7 @@ func (d *Database) createAccount( // Generate a password hash if this is not a password-less user hash := "" if plaintextPassword != "" { - hash, err = hashPassword(plaintextPassword) + hash, err = d.hashPassword(plaintextPassword) if err != nil { return nil, err } @@ -278,8 +280,8 @@ func (d *Database) GetNewNumericLocalpart( return d.accounts.selectNewNumericLocalpart(ctx, nil) } -func hashPassword(plaintext string) (hash string, err error) { - hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.DefaultCost) +func (d *Database) hashPassword(plaintext string) (hash string, err error) { + hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), d.bcryptCost) return string(hashBytes), err } diff --git a/userapi/storage/accounts/storage.go b/userapi/storage/accounts/storage.go index 3f69e95f6..28c437daa 100644 --- a/userapi/storage/accounts/storage.go +++ b/userapi/storage/accounts/storage.go @@ -27,12 +27,12 @@ import ( // NewDatabase opens a new Postgres or Sqlite database (based on dataSourceName scheme) // and sets postgres connection parameters -func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName) (Database, error) { +func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (Database, error) { switch { case dbProperties.ConnectionString.IsSQLite(): - return sqlite3.NewDatabase(dbProperties, serverName) + return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost) case dbProperties.ConnectionString.IsPostgres(): - return postgres.NewDatabase(dbProperties, serverName) + return postgres.NewDatabase(dbProperties, serverName, bcryptCost) default: return nil, fmt.Errorf("unexpected database type") } diff --git a/userapi/storage/accounts/storage_wasm.go b/userapi/storage/accounts/storage_wasm.go index dcaf371a1..8038326fe 100644 --- a/userapi/storage/accounts/storage_wasm.go +++ b/userapi/storage/accounts/storage_wasm.go @@ -25,10 +25,11 @@ import ( func NewDatabase( dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, + bcryptCost int, ) (Database, error) { switch { case dbProperties.ConnectionString.IsSQLite(): - return sqlite3.NewDatabase(dbProperties, serverName) + return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost) case dbProperties.ConnectionString.IsPostgres(): return nil, fmt.Errorf("can't use Postgres implementation") default: diff --git a/userapi/userapi.go b/userapi/userapi.go index b8b826bc8..74702020a 100644 --- a/userapi/userapi.go +++ b/userapi/userapi.go @@ -37,7 +37,6 @@ func AddInternalRoutes(router *mux.Router, intAPI api.UserInternalAPI) { func NewInternalAPI( accountDB accounts.Database, cfg *config.UserAPI, appServices []config.ApplicationService, keyAPI keyapi.KeyInternalAPI, ) api.UserInternalAPI { - deviceDB, err := devices.NewDatabase(&cfg.DeviceDatabase, cfg.Matrix.ServerName) if err != nil { logrus.WithError(err).Panicf("failed to connect to device db") diff --git a/userapi/userapi_test.go b/userapi/userapi_test.go index 25c262ad1..9a45a2dc8 100644 --- a/userapi/userapi_test.go +++ b/userapi/userapi_test.go @@ -16,6 +16,7 @@ import ( "github.com/matrix-org/dendrite/userapi/inthttp" "github.com/matrix-org/dendrite/userapi/storage/accounts" "github.com/matrix-org/gomatrixserverlib" + "golang.org/x/crypto/bcrypt" ) const ( @@ -25,7 +26,7 @@ const ( func MustMakeInternalAPI(t *testing.T) (api.UserInternalAPI, accounts.Database) { accountDB, err := accounts.NewDatabase(&config.DatabaseOptions{ ConnectionString: "file::memory:", - }, serverName) + }, serverName, bcrypt.MinCost) if err != nil { t.Fatalf("failed to create account DB: %s", err) }