Fix phpMyAdmin SSO login and harden SSO/security posture

phpMyAdmin login was broken because phpmyadmin_install.sh never wired
the private phpMyAdmin copy into the web server: no Apache Alias was
registered via DirectAdmin CustomBuild, so the SSO redirect target was
unreachable. Add configure_apache_alias()/apply_apache_alias() (Alias +
Directory blocks, ./build rewrite_confs, httpd reload), detect the real
Apache group instead of hardcoding diradmin:diradmin, stop silently
swallowing chown/chmod failures, and verify an optional SHA256 for the
downloaded phpMyAdmin tarball. Extend phpmyadmin_health_check.sh to
detect a missing/incorrect Apache alias and to probe HTTP reachability.

Security hardening: scope temporary phpMyAdmin SSO MySQL roles to
127.0.0.1 instead of '%', tighten SSO ticket file permissions to 0640
(they contain a plaintext MySQL password), and log MySQL connection
failures for diagnosis instead of swallowing them silently.

Bump version to 1.2.12 and rebuild the release archive.
This commit is contained in:
Marek Miklewicz
2026-07-04 19:16:09 +02:00
parent 78e3d66d9a
commit ba64342702
14 changed files with 390 additions and 24 deletions
+11
View File
@@ -58,6 +58,17 @@ return static function (AppContext $ctx): void {
$ensurePhpMyAdminReady();
} catch (Throwable $e) {
$message = 'Nie można przygotować phpMyAdmin: ' . $e->getMessage();
@error_log(
sprintf(
"[%s] PHPMYADMIN_PROVISION_FAIL user=%s remote=%s message=%s\n",
date('c'),
$ctx->daUser->username(),
Http::server('REMOTE_ADDR'),
$e->getMessage()
),
3,
PLUGIN_ROOT . '/error.log'
);
if ($rawMode) {
echo "HTTP/1.1 500 Internal Server Error\r\n";
echo "Content-Type: text/plain; charset=UTF-8\r\n\r\n";
+15 -5
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
final class MySQLService
{
private const TMP_ROLE_PREFIX = 'da_tmp_phpmyadmin_';
public const TMP_ROLE_HOST = '127.0.0.1';
/** @var array{host:string,port:int,database:string,user:string,password:string,socket:string} */
private array $credentials;
@@ -703,13 +704,14 @@ final class MySQLService
public function createTemporaryRole(string $role, string $password, int $expiresAt): void
{
$this->query(
'CREATE USER ' . $this->quoteLiteral($role) . '@\'%\' IDENTIFIED BY ' . $this->quoteLiteral($password)
'CREATE USER ' . $this->quoteLiteral($role) . '@' . $this->quoteLiteral(self::TMP_ROLE_HOST) .
' IDENTIFIED BY ' . $this->quoteLiteral($password)
);
$this->query(
'INSERT INTO da_plugin_sso_accounts (role_name, host_pattern, expires_at)
VALUES (?, \'%\', ?)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE expires_at = VALUES(expires_at)',
[$role, (string)$expiresAt]
[$role, self::TMP_ROLE_HOST, (string)$expiresAt]
);
}
@@ -717,13 +719,13 @@ final class MySQLService
{
$this->query(
'GRANT ALL PRIVILEGES ON ' . $this->quoteIdentifier($dbName) . '.* TO ' .
$this->quoteLiteral($role) . '@\'%\''
$this->quoteLiteral($role) . '@' . $this->quoteLiteral(self::TMP_ROLE_HOST)
);
}
public function dropTemporaryRole(string $role): void
{
$this->tryQuery('DROP USER IF EXISTS ' . $this->quoteLiteral($role) . '@\'%\'');
$this->tryQuery('DROP USER IF EXISTS ' . $this->quoteLiteral($role) . '@' . $this->quoteLiteral(self::TMP_ROLE_HOST));
$this->query('DELETE FROM da_plugin_sso_accounts WHERE role_name = ?', [$role]);
}
@@ -1155,6 +1157,14 @@ final class MySQLService
);
if ($ok !== true) {
error_log(sprintf(
'[%s] MYSQL_CONNECT_FAIL host=%s port=%d database=%s error=%s',
date('c'),
$this->credentials['host'],
$this->credentials['port'],
$targetDb,
$conn->connect_error ?? 'unknown'
));
throw new RuntimeException('Nie udało się połączyć z MySQL.');
}
+1 -1
View File
@@ -155,7 +155,7 @@ final class PhpMyAdminSso
if (@file_put_contents($tmpPath, $encoded, LOCK_EX) === false) {
throw new RuntimeException('Nie udało się zapisać ticketu phpMyAdmin.');
}
@chmod($tmpPath, 0644);
@chmod($tmpPath, 0640);
if (!@rename($tmpPath, $path)) {
@unlink($tmpPath);
throw new RuntimeException('Nie udało się aktywować ticketu phpMyAdmin.');