fix permissions of global mysql-user for customers; fixes #1286
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
@@ -228,7 +228,7 @@ if ($page == 'overview' || $page == 'mysqls') {
|
||||
$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
|
||||
|
||||
@@ -734,7 +734,7 @@ opcache.validate_timestamps'),
|
||||
('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`;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Database\DbManager;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Install\Update;
|
||||
use Froxlor\Settings;
|
||||
@@ -209,3 +210,37 @@ if (Froxlor::isDatabaseVersion('202409280')) {
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -1347,7 +1347,7 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
$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) {
|
||||
|
||||
@@ -113,9 +113,9 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
||||
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
|
||||
@@ -541,7 +541,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
||||
// 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
|
||||
|
||||
|
||||
@@ -102,8 +102,26 @@ class DbManager
|
||||
$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);
|
||||
|
||||
@@ -136,6 +154,8 @@ class DbManager
|
||||
|
||||
$dbm->getManager()->flushPrivileges();
|
||||
Database::needRoot(false);
|
||||
|
||||
unset($databases[$dbserver['dbserver']]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,13 +169,14 @@ class DbManager
|
||||
* @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') {
|
||||
@@ -184,6 +205,9 @@ class DbManager
|
||||
// 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();
|
||||
|
||||
@@ -110,13 +110,21 @@ class DbManagerMySQL
|
||||
"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', '>=')) {
|
||||
@@ -145,9 +153,10 @@ class DbManagerMySQL
|
||||
* 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+)
|
||||
@@ -167,11 +176,19 @@ class DbManagerMySQL
|
||||
} 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 . "`");
|
||||
@@ -231,8 +248,15 @@ class DbManagerMySQL
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,4 +316,50 @@ class DbManagerMySQL
|
||||
}
|
||||
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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
Reference in New Issue
Block a user