Skip to content

Commit

Permalink
fix permissions of global mysql-user for customers; fixes #1286
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Kaufmann <[email protected]>
  • Loading branch information
d00p committed Dec 3, 2024
1 parent 2bb863b commit 079047b
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 13 deletions.
2 changes: 1 addition & 1 deletion customer_mysql.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@
$new_password = Crypt::validatePassword(Request::post('mysql_password'));
foreach ($allowed_mysqlservers as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, false);
Database::needRoot(true, $dbserver, true);
// get DbManager
$dbm = new DbManager($log);
// give permission to the user on every access-host we have
Expand Down
2 changes: 1 addition & 1 deletion install/froxlor.sql.php
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@
('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.2.5'),
('panel', 'db_version', '202411200');
('panel', 'db_version', '202412030');
DROP TABLE IF EXISTS `panel_tasks`;
Expand Down
35 changes: 35 additions & 0 deletions install/updates/froxlor/update_2.2.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*/

use Froxlor\Database\Database;
use Froxlor\Database\DbManager;
use Froxlor\Froxlor;
use Froxlor\Install\Update;
use Froxlor\Settings;
Expand Down Expand Up @@ -209,3 +210,37 @@

Froxlor::updateToDbVersion('202411200');
}

if (Froxlor::isDatabaseVersion('202411200')) {

Update::showUpdateStep("Adjusting customer mysql global user");
// get all customers that are not deactivated and that have at least one database (hence a global database-user)
$customers = Database::query("
SELECT DISTINCT c.loginname, c.allowed_mysqlserver
FROM `" . TABLE_PANEL_CUSTOMERS . "` c
LEFT JOIN `" . TABLE_PANEL_DATABASES . "` d ON c.customerid = d.customerid
WHERE c.deactivated = '0' AND d.id IS NOT NULL
");
while ($customer = $customers->fetch(\PDO::FETCH_ASSOC)) {
$current_allowed_mysqlserver = !empty($customer['allowed_mysqlserver']) ? json_decode($customer['allowed_mysqlserver'], true) : [];
foreach ($current_allowed_mysqlserver as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, true);
// get DbManager
$dbm = new DbManager($this->logger());
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
if ($dbm->getManager()->userExistsOnHost($customer['loginname'], $mysql_access_host)) {
// deactivate temporarily
$dbm->getManager()->disableUser($customer['loginname'], $mysql_access_host);
// re-enable
$dbm->getManager()->enableUser($customer['loginname'], $mysql_access_host, true);
}
}
$dbm->getManager()->flushPrivileges();
Database::needRoot(false);
}
}
Update::lastStepStatus(0);

Froxlor::updateToDbVersion('202412030');
}
2 changes: 1 addition & 1 deletion lib/Froxlor/Api/Commands/Customers.php
Original file line number Diff line number Diff line change
Expand Up @@ -1347,7 +1347,7 @@ public function update()
$current_allowed_mysqlserver = isset($result['allowed_mysqlserver']) && !empty($result['allowed_mysqlserver']) ? json_decode($result['allowed_mysqlserver'], true) : [];
foreach ($current_allowed_mysqlserver as $dbserver) {
// require privileged access for target db-server
Database::needRoot(true, $dbserver, false);
Database::needRoot(true, $dbserver, true);
// get DbManager
$dbm = new DbManager($this->logger());
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
Expand Down
6 changes: 3 additions & 3 deletions lib/Froxlor/Api/Commands/Mysqls.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ public function add()
if (strlen($newdb_params['loginname'] . '_' . $databasename) > Database::getSqlUsernameLength()) {
throw new Exception("Database name cannot be longer than " . (Database::getSqlUsernameLength() - strlen($newdb_params['loginname'] . '_')) . " characters.", 406);
}
$username = $dbm->createDatabase($newdb_params['loginname'] . '_' . $databasename, $password, $dbserver);
$username = $dbm->createDatabase($newdb_params['loginname'] . '_' . $databasename, $password, $dbserver, 0, $newdb_params['loginname']);
} else {
$username = $dbm->createDatabase($newdb_params['loginname'], $password, $dbserver, $newdb_params['mysql_lastaccountnumber']);
$username = $dbm->createDatabase($newdb_params['loginname'], $password, $dbserver, $newdb_params['mysql_lastaccountnumber'], $newdb_params['loginname']);
}

// we've checked against the password in dbm->createDatabase
Expand Down Expand Up @@ -541,7 +541,7 @@ public function delete()
// Begin root-session
Database::needRoot(true, $result['dbserver'], false);
$dbm = new DbManager($this->logger());
$dbm->getManager()->deleteDatabase($result['databasename']);
$dbm->getManager()->deleteDatabase($result['databasename'], $customer['loginname']);
Database::needRoot(false);
// End root-session

Expand Down
28 changes: 26 additions & 2 deletions lib/Froxlor/Database/DbManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,26 @@ public static function correctMysqlUsers(array $mysql_access_host_array)
$databases[$databases_row['dbserver']][] = $databases_row['databasename'];
}

$customers_sel = Database::query("
SELECT DISTINCT c.loginname
FROM `" . TABLE_PANEL_CUSTOMERS . "` c
LEFT JOIN `" . TABLE_PANEL_DATABASES . "` d ON c.customerid = d.customerid
WHERE c.deactivated = '0' AND d.id IS NOT NULL
");
$customers = [];
while ($customer = $customers_sel->fetch(\PDO::FETCH_ASSOC)) {
$customers[] = $customer['loginname'];
}

$dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`");
while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {

// add all customer loginnames to the $databases array for this database-server to correct
// a possible existing global mysql-user for that customer
foreach ($customers as $customer) {
$databases[$dbserver['dbserver']][] = $customer;
}

// require privileged access for target db-server
Database::needRoot(true, $dbserver['dbserver'], false);

Expand Down Expand Up @@ -136,6 +154,8 @@ public static function correctMysqlUsers(array $mysql_access_host_array)

$dbm->getManager()->flushPrivileges();
Database::needRoot(false);

unset($databases[$dbserver['dbserver']]);
}
}

Expand All @@ -149,13 +169,14 @@ public static function correctMysqlUsers(array $mysql_access_host_array)
* @param ?string $password
* @param int $dbserver
* @param int $last_accnumber
* @param ?string $global_user
*
* @return string|bool $username if successful or false of username is equal to the password
* @throws Exception
*/
public function createDatabase(string $loginname = null, string $password = null, int $dbserver = 0, int $last_accnumber = 0)
public function createDatabase(string $loginname = null, string $password = null, int $dbserver = 0, int $last_accnumber = 0, string $global_user = "")
{
Database::needRoot(true, $dbserver, false);
Database::needRoot(true, $dbserver, true);

// check whether we shall create a random username
if (strtoupper(Settings::Get('customer.mysqlprefix')) == 'RANDOM') {
Expand Down Expand Up @@ -184,6 +205,9 @@ public function createDatabase(string $loginname = null, string $password = null
// and give permission to the user on every access-host we have
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
$this->getManager()->grantPrivilegesTo($username, $password, $mysql_access_host);
if (!empty($global_user)) {
$this->getManager()->grantCreateToDb($global_user, $username, $mysql_access_host);
}
}

$this->getManager()->flushPrivileges();
Expand Down
78 changes: 74 additions & 4 deletions lib/Froxlor/Database/Manager/DbManagerMySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,21 @@ public function grantPrivilegesTo(string $username, $password, string $access_ho
"password" => $password
]);
// grant privileges
$grants = "ALL";
if ($grant_access_prefix) {
$grants = "SELECT, INSERT, UPDATE, DELETE, DROP, INDEX, ALTER";
}
$stmt = Database::prepare("
GRANT ALL ON `" . $username . ($grant_access_prefix ? '%' : '') . "`.* TO :username@:host
GRANT " . $grants . " ON `" . $username . ($grant_access_prefix ? '%' : '') . "`.* TO :username@:host
");
Database::pexecute($stmt, [
"username" => $username,
"host" => $access_host
]);

if ($grant_access_prefix) {
$this->grantCreateToCustomerDbs($username, $access_host);
}
} else {
// set password
if (version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '5.7.6', '<') || version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '10.0.0', '>=')) {
Expand Down Expand Up @@ -145,9 +153,10 @@ public function grantPrivilegesTo(string $username, $password, string $access_ho
* takes away any privileges from a user to that db
*
* @param string $dbname
* @param ?string $global_user
* @throws \Exception
*/
public function deleteDatabase(string $dbname)
public function deleteDatabase(string $dbname, string $global_user = "")
{
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
// failsafe if user has been deleted manually (requires MySQL 4.1.2+)
Expand All @@ -167,11 +176,19 @@ public function deleteDatabase(string $dbname)
} else {
$drop_stmt = Database::prepare("DROP USER IF EXISTS :dbname@:host");
}
$rev_stmt = Database::prepare("REVOKE ALL PRIVILEGES ON `" . $dbname . "`.* FROM :guser@:host;");
while ($host = $host_res_stmt->fetch(PDO::FETCH_ASSOC)) {
Database::pexecute($drop_stmt, [
'dbname' => $dbname,
'host' => $host['Host']
], false);

if (!empty($global_user)) {
Database::pexecute($rev_stmt, [
'guser' => $global_user,
'host' => $host['Host']
], false);
}
}

$drop_stmt = Database::prepare("DROP DATABASE IF EXISTS `" . $dbname . "`");
Expand Down Expand Up @@ -231,8 +248,15 @@ public function enableUser(string $username, string $host, bool $grant_access_pr
{
// check whether user exists to avoid errors
if ($this->userExistsOnHost($username, $host)) {
Database::query('GRANT ALL PRIVILEGES ON `' . $username . ($grant_access_prefix ? '%' : '') . '`.* TO `' . $username . '`@`' . $host . '`');
Database::query('GRANT ALL PRIVILEGES ON `' . str_replace('_', '\_', $username) . ($grant_access_prefix ? '%' : '') . '` . * TO `' . $username . '`@`' . $host . '`');
$grants = "ALL PRIVILEGES";
if ($grant_access_prefix) {
$grants = "SELECT, INSERT, UPDATE, DELETE, DROP, INDEX, ALTER";
}
Database::query('GRANT ' . $grants . ' ON `' . $username . ($grant_access_prefix ? '%' : '') . '`.* TO `' . $username . '`@`' . $host . '`');
Database::query('GRANT ' . $grants . ' ON `' . str_replace('_', '\_', $username) . ($grant_access_prefix ? '%' : '') . '` . * TO `' . $username . '`@`' . $host . '`');
if ($grant_access_prefix) {
$this->grantCreateToCustomerDbs($username, $host);
}
}
}

Expand Down Expand Up @@ -292,4 +316,50 @@ public function getAllSqlUsers(bool $user_only = true): array
}
return $allsqlusers;
}

/**
* grant "CREATE" for prefix user to all existing databases of that customer
*
* @param string $username
* @param string $access_host
* @return void
* @throws \Exception
*/
private function grantCreateToCustomerDbs(string $username, string $access_host)
{
$cus_stmt = Database::prepare("SELECT customerid FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE loginname = :username");
$cust = Database::pexecute_first($cus_stmt, ['username' => $username]);
if ($cust) {
$sel_stmt = Database::prepare("SELECT databasename FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :cid");
Database::pexecute($sel_stmt, ['cid' => $cust['customerid']]);
while ($dbdata = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$stmt = Database::prepare("
GRANT CREATE ON `" . $dbdata['databasename'] . "`.* TO :username@:host
");
Database::pexecute($stmt, [
"username" => $username,
"host" => $access_host
]);
}
}
}

/**
* grant "CREATE" for prefix user to all existing databases of that customer
*
* @param string $username
* @param string $access_host
* @return void
* @throws \Exception
*/
public function grantCreateToDb(string $username, string $database, string $access_host)
{
$stmt = Database::prepare("
GRANT CREATE ON `" . $database . "`.* TO :username@:host
");
Database::pexecute($stmt, [
"username" => $username,
"host" => $access_host
]);
}
}
2 changes: 1 addition & 1 deletion lib/Froxlor/Froxlor.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class Froxlor
const VERSION = '2.2.5';

// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202411200';
const DBVERSION = '202412030';

// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';
Expand Down

0 comments on commit 079047b

Please sign in to comment.