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'));
|
$new_password = Crypt::validatePassword(Request::post('mysql_password'));
|
||||||
foreach ($allowed_mysqlservers as $dbserver) {
|
foreach ($allowed_mysqlservers as $dbserver) {
|
||||||
// require privileged access for target db-server
|
// require privileged access for target db-server
|
||||||
Database::needRoot(true, $dbserver, false);
|
Database::needRoot(true, $dbserver, true);
|
||||||
// get DbManager
|
// get DbManager
|
||||||
$dbm = new DbManager($log);
|
$dbm = new DbManager($log);
|
||||||
// give permission to the user on every access-host we have
|
// give permission to the user on every access-host we have
|
||||||
|
|||||||
@@ -734,7 +734,7 @@ opcache.validate_timestamps'),
|
|||||||
('panel', 'settings_mode', '0'),
|
('panel', 'settings_mode', '0'),
|
||||||
('panel', 'menu_collapsed', '1'),
|
('panel', 'menu_collapsed', '1'),
|
||||||
('panel', 'version', '2.2.5'),
|
('panel', 'version', '2.2.5'),
|
||||||
('panel', 'db_version', '202411200');
|
('panel', 'db_version', '202412030');
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `panel_tasks`;
|
DROP TABLE IF EXISTS `panel_tasks`;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use Froxlor\Database\Database;
|
use Froxlor\Database\Database;
|
||||||
|
use Froxlor\Database\DbManager;
|
||||||
use Froxlor\Froxlor;
|
use Froxlor\Froxlor;
|
||||||
use Froxlor\Install\Update;
|
use Froxlor\Install\Update;
|
||||||
use Froxlor\Settings;
|
use Froxlor\Settings;
|
||||||
@@ -209,3 +210,37 @@ if (Froxlor::isDatabaseVersion('202409280')) {
|
|||||||
|
|
||||||
Froxlor::updateToDbVersion('202411200');
|
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) : [];
|
$current_allowed_mysqlserver = isset($result['allowed_mysqlserver']) && !empty($result['allowed_mysqlserver']) ? json_decode($result['allowed_mysqlserver'], true) : [];
|
||||||
foreach ($current_allowed_mysqlserver as $dbserver) {
|
foreach ($current_allowed_mysqlserver as $dbserver) {
|
||||||
// require privileged access for target db-server
|
// require privileged access for target db-server
|
||||||
Database::needRoot(true, $dbserver, false);
|
Database::needRoot(true, $dbserver, true);
|
||||||
// get DbManager
|
// get DbManager
|
||||||
$dbm = new DbManager($this->logger());
|
$dbm = new DbManager($this->logger());
|
||||||
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
|
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()) {
|
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);
|
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 {
|
} 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
|
// we've checked against the password in dbm->createDatabase
|
||||||
@@ -541,7 +541,7 @@ class Mysqls extends ApiCommand implements ResourceEntity
|
|||||||
// Begin root-session
|
// Begin root-session
|
||||||
Database::needRoot(true, $result['dbserver'], false);
|
Database::needRoot(true, $result['dbserver'], false);
|
||||||
$dbm = new DbManager($this->logger());
|
$dbm = new DbManager($this->logger());
|
||||||
$dbm->getManager()->deleteDatabase($result['databasename']);
|
$dbm->getManager()->deleteDatabase($result['databasename'], $customer['loginname']);
|
||||||
Database::needRoot(false);
|
Database::needRoot(false);
|
||||||
// End root-session
|
// End root-session
|
||||||
|
|
||||||
|
|||||||
@@ -102,8 +102,26 @@ class DbManager
|
|||||||
$databases[$databases_row['dbserver']][] = $databases_row['databasename'];
|
$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 . "`");
|
$dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`");
|
||||||
while ($dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC)) {
|
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
|
// require privileged access for target db-server
|
||||||
Database::needRoot(true, $dbserver['dbserver'], false);
|
Database::needRoot(true, $dbserver['dbserver'], false);
|
||||||
|
|
||||||
@@ -136,6 +154,8 @@ class DbManager
|
|||||||
|
|
||||||
$dbm->getManager()->flushPrivileges();
|
$dbm->getManager()->flushPrivileges();
|
||||||
Database::needRoot(false);
|
Database::needRoot(false);
|
||||||
|
|
||||||
|
unset($databases[$dbserver['dbserver']]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,13 +169,14 @@ class DbManager
|
|||||||
* @param ?string $password
|
* @param ?string $password
|
||||||
* @param int $dbserver
|
* @param int $dbserver
|
||||||
* @param int $last_accnumber
|
* @param int $last_accnumber
|
||||||
|
* @param ?string $global_user
|
||||||
*
|
*
|
||||||
* @return string|bool $username if successful or false of username is equal to the password
|
* @return string|bool $username if successful or false of username is equal to the password
|
||||||
* @throws Exception
|
* @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
|
// check whether we shall create a random username
|
||||||
if (strtoupper(Settings::Get('customer.mysqlprefix')) == 'RANDOM') {
|
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
|
// 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) {
|
foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) {
|
||||||
$this->getManager()->grantPrivilegesTo($username, $password, $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();
|
$this->getManager()->flushPrivileges();
|
||||||
|
|||||||
@@ -110,13 +110,21 @@ class DbManagerMySQL
|
|||||||
"password" => $password
|
"password" => $password
|
||||||
]);
|
]);
|
||||||
// grant privileges
|
// grant privileges
|
||||||
|
$grants = "ALL";
|
||||||
|
if ($grant_access_prefix) {
|
||||||
|
$grants = "SELECT, INSERT, UPDATE, DELETE, DROP, INDEX, ALTER";
|
||||||
|
}
|
||||||
$stmt = Database::prepare("
|
$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, [
|
Database::pexecute($stmt, [
|
||||||
"username" => $username,
|
"username" => $username,
|
||||||
"host" => $access_host
|
"host" => $access_host
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($grant_access_prefix) {
|
||||||
|
$this->grantCreateToCustomerDbs($username, $access_host);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// set password
|
// 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', '>=')) {
|
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
|
* takes away any privileges from a user to that db
|
||||||
*
|
*
|
||||||
* @param string $dbname
|
* @param string $dbname
|
||||||
|
* @param ?string $global_user
|
||||||
* @throws \Exception
|
* @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', '<')) {
|
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
|
||||||
// failsafe if user has been deleted manually (requires MySQL 4.1.2+)
|
// failsafe if user has been deleted manually (requires MySQL 4.1.2+)
|
||||||
@@ -167,11 +176,19 @@ class DbManagerMySQL
|
|||||||
} else {
|
} else {
|
||||||
$drop_stmt = Database::prepare("DROP USER IF EXISTS :dbname@:host");
|
$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)) {
|
while ($host = $host_res_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
Database::pexecute($drop_stmt, [
|
Database::pexecute($drop_stmt, [
|
||||||
'dbname' => $dbname,
|
'dbname' => $dbname,
|
||||||
'host' => $host['Host']
|
'host' => $host['Host']
|
||||||
], false);
|
], 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 . "`");
|
$drop_stmt = Database::prepare("DROP DATABASE IF EXISTS `" . $dbname . "`");
|
||||||
@@ -231,8 +248,15 @@ class DbManagerMySQL
|
|||||||
{
|
{
|
||||||
// check whether user exists to avoid errors
|
// check whether user exists to avoid errors
|
||||||
if ($this->userExistsOnHost($username, $host)) {
|
if ($this->userExistsOnHost($username, $host)) {
|
||||||
Database::query('GRANT ALL PRIVILEGES ON `' . $username . ($grant_access_prefix ? '%' : '') . '`.* TO `' . $username . '`@`' . $host . '`');
|
$grants = "ALL PRIVILEGES";
|
||||||
Database::query('GRANT ALL PRIVILEGES ON `' . str_replace('_', '\_', $username) . ($grant_access_prefix ? '%' : '') . '` . * TO `' . $username . '`@`' . $host . '`');
|
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;
|
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';
|
const VERSION = '2.2.5';
|
||||||
|
|
||||||
// Database version (YYYYMMDDC where C is a daily counter)
|
// Database version (YYYYMMDDC where C is a daily counter)
|
||||||
const DBVERSION = '202411200';
|
const DBVERSION = '202412030';
|
||||||
|
|
||||||
// Distribution branding-tag (used for Debian etc.)
|
// Distribution branding-tag (used for Debian etc.)
|
||||||
const BRANDING = '';
|
const BRANDING = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user