From f0e4764a116927ac3ae807ac13540509e560115f Mon Sep 17 00:00:00 2001 From: AreYouScared Date: Wed, 22 Apr 2020 06:00:04 -0400 Subject: [PATCH 1/5] 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 e04c5a86..d0d982ef 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 2db45734..6b8b1664 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 c26be52f..eb19dd4b 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 0f07ad70..b98a757d 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 6abb0a49..2a9ebcab 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 1cdced61..a88ba6e8 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 00000000..e86622d3 --- /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 a80b304c..f58d6946 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 7798487c..6b313375 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

+
-

{database.max_connections}

+

{database.maxConnections}

Max Connections

From 82dd7dc8e3b913839685728fb41f34139ee1d871 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Thu, 23 Apr 2020 10:45:44 -0400 Subject: [PATCH 3/5] Allow Null = 0 Allow Value to be nullable, will autofill 0 if value is null or 0, to facilitate "unlimited" connections. --- .../Repository/DatabaseRepositoryInterface.php | 2 +- .../Servers/Databases/StoreServerDatabaseRequest.php | 1 + app/Models/Database.php | 2 +- app/Repositories/Eloquent/DatabaseRepository.php | 10 +++++++--- .../2020_04_22_055500_add_max_connections_column.php | 2 +- .../components/server/databases/DatabaseRow.tsx | 5 ++++- resources/views/admin/databases/view.blade.php | 6 +++++- .../views/admin/servers/view/database.blade.php | 12 ++++++++---- 8 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/Contracts/Repository/DatabaseRepositoryInterface.php b/app/Contracts/Repository/DatabaseRepositoryInterface.php index d0d982ef..967ca20f 100644 --- a/app/Contracts/Repository/DatabaseRepositoryInterface.php +++ b/app/Contracts/Repository/DatabaseRepositoryInterface.php @@ -68,7 +68,7 @@ interface DatabaseRepositoryInterface extends RepositoryInterface * @param string $username * @param string $remote * @param string $password - * @param string $max_connections + * @param $max_connections * @return bool */ public function createUser(string $username, string $remote, string $password, string $max_connections): bool; diff --git a/app/Http/Requests/Admin/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Admin/Servers/Databases/StoreServerDatabaseRequest.php index ba8b76a8..40a4b06b 100644 --- a/app/Http/Requests/Admin/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Admin/Servers/Databases/StoreServerDatabaseRequest.php @@ -25,6 +25,7 @@ class StoreServerDatabaseRequest extends AdminFormRequest $query->where('database_host_id', $this->input('database_host_id') ?? 0); }), ], + 'max_connections' => 'nullable', 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', 'database_host_id' => 'required|integer|exists:database_hosts,id', ]; diff --git a/app/Models/Database.php b/app/Models/Database.php index 6b8b1664..d2a7377f 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -51,7 +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', + 'max_connections' => 'nullable', '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 eb19dd4b..df9dbb6e 100644 --- a/app/Repositories/Eloquent/DatabaseRepository.php +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -135,12 +135,16 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor * @param string $username * @param string $remote * @param string $password - * @param string $max_connections + * @param $max_connections * @return bool */ - public function createUser(string $username, string $remote, string $password, string $max_connections): bool + public function createUser(string $username, string $remote, string $password, $max_connections): bool { - return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\' WITH MAX_USER_CONNECTIONS %s', $username, $remote, $password, $max_connections)); + if (!$max_connections) { + return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password)); + } else { + return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\' WITH MAX_USER_CONNECTIONS %s', $username, $remote, $password, $max_connections)); + } } /** 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 index e86622d3..9af4f468 100644 --- a/database/migrations/2020_04_22_055500_add_max_connections_column.php +++ b/database/migrations/2020_04_22_055500_add_max_connections_column.php @@ -14,7 +14,7 @@ class AddMaxConnectionsColumnToDatabasesTable extends Migration public function up() { Schema::table('databases', function (Blueprint $table) { - $table->integer('max_connections')->nullable(false)->default(0)->after('password'); + $table->integer('max_connections')->nullable()->default(0)->after('password'); }); } diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index b1d54911..424f4498 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -51,7 +51,10 @@ export default ({ database, className }: Props) => { addError({ key: 'database:delete', message: httpErrorToHuman(error) }); }); }; - + if (!database.maxConnections){ + database.maxConnections = "Unlimited" + } + return ( {{ $database->database }} {{ $database->username }} {{ $database->remote }} - {{ $database->max_connections }} + @if($database->max_connections != null) + {{ $database->max_connections }} + @else + Unlimited + @endif diff --git a/resources/views/admin/servers/view/database.blade.php b/resources/views/admin/servers/view/database.blade.php index 6b313375..f861008e 100644 --- a/resources/views/admin/servers/view/database.blade.php +++ b/resources/views/admin/servers/view/database.blade.php @@ -46,7 +46,11 @@ {{ $database->username }} {{ $database->remote }} {{ $database->host->host }}:{{ $database->host->port }} - {{ $database->max_connections }} + @if($database->max_connections != null) + {{ $database->max_connections }} + @else + Unlimited + @endif @@ -86,9 +90,9 @@

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

+ + +

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

-
-

{database.maxConnections}

-

Max Connections

-