From f0e4764a116927ac3ae807ac13540509e560115f Mon Sep 17 00:00:00 2001 From: AreYouScared Date: Wed, 22 Apr 2020 06:00:04 -0400 Subject: [PATCH] Add Max Concurrent Connections for database users Closes #1849 Allows database users to be limited to a number of concurrent connections to prevent one user from connecting hundreds of time and bottlenecking the MySQL server. --- .../DatabaseRepositoryInterface.php | 3 +- app/Models/Database.php | 3 +- .../Eloquent/DatabaseRepository.php | 5 +-- .../Databases/DatabaseManagementService.php | 3 +- .../Databases/DatabasePasswordService.php | 2 +- .../Application/ServerDatabaseTransformer.php | 1 + ...4_22_055500_add_max_connections_column.php | 32 +++++++++++++++++++ .../server/databases/DatabaseRow.tsx | 4 +++ .../views/admin/databases/view.blade.php | 2 ++ .../admin/servers/view/database.blade.php | 7 ++++ 10 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 database/migrations/2020_04_22_055500_add_max_connections_column.php diff --git a/app/Contracts/Repository/DatabaseRepositoryInterface.php b/app/Contracts/Repository/DatabaseRepositoryInterface.php index e04c5a862..d0d982ef8 100644 --- a/app/Contracts/Repository/DatabaseRepositoryInterface.php +++ b/app/Contracts/Repository/DatabaseRepositoryInterface.php @@ -68,9 +68,10 @@ interface DatabaseRepositoryInterface extends RepositoryInterface * @param string $username * @param string $remote * @param string $password + * @param string $max_connections * @return bool */ - public function createUser(string $username, string $remote, string $password): bool; + public function createUser(string $username, string $remote, string $password, string $max_connections): bool; /** * Give a specific user access to a given database. diff --git a/app/Models/Database.php b/app/Models/Database.php index 2db45734d..6b8b16641 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -30,7 +30,7 @@ class Database extends Model * @var array */ protected $fillable = [ - 'server_id', 'database_host_id', 'database', 'username', 'password', 'remote', + 'server_id', 'database_host_id', 'database', 'username', 'password', 'remote', 'max_connections', ]; /** @@ -51,6 +51,7 @@ class Database extends Model 'database_host_id' => 'required|exists:database_hosts,id', 'database' => 'required|string|alpha_dash|between:3,100', 'username' => 'string|alpha_dash|between:3,100', + 'max_connections' => 'string', 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', 'password' => 'string', ]; diff --git a/app/Repositories/Eloquent/DatabaseRepository.php b/app/Repositories/Eloquent/DatabaseRepository.php index c26be52f3..eb19dd4b0 100644 --- a/app/Repositories/Eloquent/DatabaseRepository.php +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -135,11 +135,12 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor * @param string $username * @param string $remote * @param string $password + * @param string $max_connections * @return bool */ - public function createUser(string $username, string $remote, string $password): bool + public function createUser(string $username, string $remote, string $password, string $max_connections): bool { - return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password)); + return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\' WITH MAX_USER_CONNECTIONS %s', $username, $remote, $password, $max_connections)); } /** diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php index 0f07ad704..b98a757db 100644 --- a/app/Services/Databases/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -84,7 +84,8 @@ class DatabaseManagementService $this->repository->createUser( $database->username, $database->remote, - $this->encrypter->decrypt($database->password) + $this->encrypter->decrypt($database->password), + $database->max_connections ); $this->repository->assignUserToDatabase( $database->database, diff --git a/app/Services/Databases/DatabasePasswordService.php b/app/Services/Databases/DatabasePasswordService.php index 6abb0a499..2a9ebcabe 100644 --- a/app/Services/Databases/DatabasePasswordService.php +++ b/app/Services/Databases/DatabasePasswordService.php @@ -71,7 +71,7 @@ class DatabasePasswordService ]); $this->repository->dropUser($database->username, $database->remote); - $this->repository->createUser($database->username, $database->remote, $password); + $this->repository->createUser($database->username, $database->remote, $password, $database->max_connections); $this->repository->assignUserToDatabase($database->database, $database->username, $database->remote); $this->repository->flush(); }); diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php index 1cdced612..a88ba6e8c 100644 --- a/app/Transformers/Api/Application/ServerDatabaseTransformer.php +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -56,6 +56,7 @@ class ServerDatabaseTransformer extends BaseTransformer 'database' => $model->database, 'username' => $model->username, 'remote' => $model->remote, + 'max_connections' => $model->max_connections, 'created_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $model->created_at) ->setTimezone(config('app.timezone')) ->toIso8601String(), diff --git a/database/migrations/2020_04_22_055500_add_max_connections_column.php b/database/migrations/2020_04_22_055500_add_max_connections_column.php new file mode 100644 index 000000000..e86622d37 --- /dev/null +++ b/database/migrations/2020_04_22_055500_add_max_connections_column.php @@ -0,0 +1,32 @@ +integer('max_connections')->nullable(false)->default(0)->after('password'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('databases', function (Blueprint $table) { + $table->dropColumn('max_connections'); + }); + } +} diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index a80b304c3..f58d69461 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -151,6 +151,10 @@ export default ({ database, className }: Props) => {

{database.username}

Username

+
+

{database.max_connections}

+

Max Connections

+
diff --git a/resources/views/admin/servers/view/database.blade.php b/resources/views/admin/servers/view/database.blade.php index 7798487c2..6b3133755 100644 --- a/resources/views/admin/servers/view/database.blade.php +++ b/resources/views/admin/servers/view/database.blade.php @@ -37,6 +37,7 @@ Username Connections From Host + Max Conenctions @foreach($server->databases as $database) @@ -45,6 +46,7 @@ {{ $database->username }} {{ $database->remote }} {{ $database->host->host }}:{{ $database->host->port }} + {{ $database->max_connections }} @@ -83,6 +85,11 @@

This should reflect the IP address that connections are allowed from. Uses standard MySQL notation. If unsure leave as %.

+
+ + +

This should reflect the max number of concurrent connections from this user to the database. Use 0 for unlimited

+