Skip to content

Commit

Permalink
Add mutual TLS (mTLS) support for remote database connections in PhpM…
Browse files Browse the repository at this point in the history
…yAdmin (#448)

Add mutual TLS (mTLS) support for remote database connections in PhpMyAdmin

Pull-request: #448

Signed-off-by: lordrobincbz <[email protected]>
Co-authored-by: William Desportes <[email protected]>
  • Loading branch information
LordRobinCbz and williamdes authored Jan 7, 2025
1 parent 12b0f34 commit e1fe998
Show file tree
Hide file tree
Showing 16 changed files with 534 additions and 6 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ __pycache__
.pytest_cache
.vscode
.history
.venv
.venv
testing/*.pem
4 changes: 4 additions & 0 deletions Dockerfile-alpine.template
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ RUN set -ex; \

# set recommended PHP.ini settings
# see https://secure.php.net/manual/en/opcache.installation.php
ENV PMA_SSL_DIR /etc/phpmyadmin/ssl
ENV MAX_EXECUTION_TIME 600
ENV MEMORY_LIMIT 512M
ENV UPLOAD_LIMIT 2048K
Expand Down Expand Up @@ -91,8 +92,11 @@ RUN set -ex; \
gnupg \
; \
mkdir $SESSION_SAVE_PATH; \
mkdir -p $PMA_SSL_DIR; \
chmod 1777 $SESSION_SAVE_PATH; \
chmod 755 $PMA_SSL_DIR; \
chown www-data:www-data $SESSION_SAVE_PATH; \
chown www-data:www-data $PMA_SSL_DIR; \
\
export GNUPGHOME="$(mktemp -d)"; \
export GPGKEY="%%GPG_KEY%%"; \
Expand Down
4 changes: 4 additions & 0 deletions Dockerfile-debian.template
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ RUN set -ex; \

# set recommended PHP.ini settings
# see https://secure.php.net/manual/en/opcache.installation.php
ENV PMA_SSL_DIR /etc/phpmyadmin/ssl
ENV MAX_EXECUTION_TIME 600
ENV MEMORY_LIMIT 512M
ENV UPLOAD_LIMIT 2048K
Expand Down Expand Up @@ -107,8 +108,11 @@ RUN set -ex; \
dirmngr \
; \
mkdir $SESSION_SAVE_PATH; \
mkdir -p $PMA_SSL_DIR; \
chmod 1777 $SESSION_SAVE_PATH; \
chmod 755 $PMA_SSL_DIR; \
chown www-data:www-data $SESSION_SAVE_PATH; \
chown www-data:www-data $PMA_SSL_DIR; \
\
export GNUPGHOME="$(mktemp -d)"; \
export GPGKEY="%%GPG_KEY%%"; \
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,17 @@ docker run --name phpmyadmin -d -e PMA_HOSTS='sslhost,nosslhost' -e PMA_SSLS='1,
* ``PMA_PORTS`` - define comma separated list of ports of the MySQL servers
* ``PMA_SOCKET`` - define socket file for the MySQL connection
* ``PMA_SOCKETS`` - define comma separated list of socket files for the MySQL connections
* ``PMA_SSL_DIR`` - define the path used for SSL files generated from environement variables, default value is `/etc/phpmyadmin/ssl`
* ``PMA_SSL`` - when set to 1, defines SSL usage for the MySQL connection
* ``PMA_SSLS`` - comma separated list of `0` and `1` defining SSL usage for the corresponding MySQL connections
* ``PMA_SSL_VERIFY`` - when set to 1, enables SSL certificate verification for the MySQL connection.
* ``PMA_SSL_VERIFIES`` - comma-separated list of `0` and `1` to enable or disable SSL certificate verification for multiple MySQL connections.
* ``PMA_SSL_CA`` - in the context of mutual TLS security, allows setting your CA certificate file as a string inside the default `config.inc.php`.
* ``PMA_SSL_CAS`` - in the context of mutual TLS security, allows setting multiple CA certificate files as a comma-separated list of strings inside the default `config.inc.php`.
* ``PMA_SSL_CERT`` - in the context of mutual TLS security, allows setting your certificate file as a string inside the default `config.inc.php`.
* ``PMA_SSL_CERTS`` - in the context of mutual TLS security, allows setting multiple certificate files as a comma-separated list of strings inside the default `config.inc.php`.
* ``PMA_SSL_KEY`` - in the context of mutual TLS security, allows setting your private key file as a string inside the default `config.inc.php`.
* ``PMA_SSL_KEYS`` - in the context of mutual TLS security, allows setting multiple private key files as a comma-separated list of strings inside the default `config.inc.php`.
* ``PMA_USER`` and ``PMA_PASSWORD`` - define username and password to use only with the `config` authentication method
* ``PMA_ABSOLUTE_URI`` - the full URL to phpMyAdmin. Sometimes needed when used in a reverse-proxy configuration. Don't set this unless needed. See [documentation](https://docs.phpmyadmin.net/en/latest/config.html#cfg_PmaAbsoluteUri).
* ``PMA_CONFIG_BASE64`` - if set, this option will override the default `config.inc.php` with the base64 decoded contents of the variable
Expand All @@ -212,6 +221,19 @@ For usage with Docker secrets, appending ``_FILE`` to the ``PMA_PASSWORD`` envir
docker run --name phpmyadmin -d -e PMA_PASSWORD_FILE=/run/secrets/db_password.txt -p 8080:80 phpmyadmin:latest
```

#### Variables that can store the file contents using ``_BASE64``

- `PMA_SSL_CA`
- `PMA_SSL_CAS`
- `PMA_SSL_KEY`
- `PMA_SSL_KEYS`
- `PMA_SSL_CERT`
- `PMA_SSL_CERTS`

Also includes: `PMA_CONFIG_BASE64` or `PMA_USER_CONFIG_BASE64`.

For example, the variable would be named `PMA_SSL_CA_BASE64` and the value is the base64 encoded contents of the file.

#### Variables that can be read from a file using ``_FILE``

- `MYSQL_ROOT_PASSWORD`
Expand Down
4 changes: 4 additions & 0 deletions apache/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ RUN set -ex; \

# set recommended PHP.ini settings
# see https://secure.php.net/manual/en/opcache.installation.php
ENV PMA_SSL_DIR /etc/phpmyadmin/ssl
ENV MAX_EXECUTION_TIME 600
ENV MEMORY_LIMIT 512M
ENV UPLOAD_LIMIT 2048K
Expand Down Expand Up @@ -108,8 +109,11 @@ RUN set -ex; \
dirmngr \
; \
mkdir $SESSION_SAVE_PATH; \
mkdir -p $PMA_SSL_DIR; \
chmod 1777 $SESSION_SAVE_PATH; \
chmod 755 $PMA_SSL_DIR; \
chown www-data:www-data $SESSION_SAVE_PATH; \
chown www-data:www-data $PMA_SSL_DIR; \
\
export GNUPGHOME="$(mktemp -d)"; \
export GPGKEY="3D06A59ECE730EB71B511C17CE752F178259BD92"; \
Expand Down
72 changes: 71 additions & 1 deletion apache/config.inc.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

require '/etc/phpmyadmin/config.secret.inc.php';
require_once '/etc/phpmyadmin/config.secret.inc.php';
require_once '/etc/phpmyadmin/helpers.php';

/* Ensure we got the environment */
$vars = [
Expand Down Expand Up @@ -29,6 +30,21 @@
'PMA_SAVEDIR',
'PMA_SSL',
'PMA_SSLS',
'PMA_SSL_DIR',
'PMA_SSL_VERIFY',
'PMA_SSL_VERIFIES',
'PMA_SSL_CA',
'PMA_SSL_CAS',
'PMA_SSL_CA_BASE64',
'PMA_SSL_CAS_BASE64',
'PMA_SSL_KEY',
'PMA_SSL_KEYS',
'PMA_SSL_KEY_BASE64',
'PMA_SSL_KEYS_BASE64',
'PMA_SSL_CERT',
'PMA_SSL_CERTS',
'PMA_SSL_CERT_BASE64',
'PMA_SSL_CERTS_BASE64',
];

foreach ($vars as $var) {
Expand All @@ -37,6 +53,11 @@
$_ENV[$var] = $env;
}
}

if (! defined('PMA_SSL_DIR')) {
define('PMA_SSL_DIR', $_ENV['PMA_SSL_DIR'] ?? '/etc/phpmyadmin/ssl');
}

if (isset($_ENV['PMA_QUERYHISTORYDB'])) {
$cfg['QueryHistoryDB'] = (bool) $_ENV['PMA_QUERYHISTORYDB'];
}
Expand All @@ -55,6 +76,35 @@
$cfg['PmaAbsoluteUri'] = trim($_ENV['PMA_ABSOLUTE_URI']);
}

if (isset($_ENV['PMA_SSL_CA_BASE64'])) {
$_ENV['PMA_SSL_CA'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CA_BASE64'], 'phpmyadmin-ssl-CA', 'pem', PMA_SSL_DIR);
}

/* Decode and save the SSL key from base64 */
if (isset($_ENV['PMA_SSL_KEY_BASE64'])) {
$_ENV['PMA_SSL_KEY'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_KEY_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
}

/* Decode and save the SSL certificate from base64 */
if (isset($_ENV['PMA_SSL_CERT_BASE64'])) {
$_ENV['PMA_SSL_CERT'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CERT_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
}

/* Decode and save multiple SSL CA certificates from base64 */
if (isset($_ENV['PMA_SSL_CAS_BASE64'])) {
$_ENV['PMA_SSL_CAS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CAS_BASE64'], 'phpmyadmin-ssl-CA', 'pem', PMA_SSL_DIR);
}

/* Decode and save multiple SSL keys from base64 */
if (isset($_ENV['PMA_SSL_KEYS_BASE64'])) {
$_ENV['PMA_SSL_KEYS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_KEYS_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
}

/* Decode and save multiple SSL certificates from base64 */
if (isset($_ENV['PMA_SSL_CERTS_BASE64'])) {
$_ENV['PMA_SSL_CERTS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CERTS_BASE64'], 'phpmyadmin-ssl-KEY', 'key', PMA_SSL_DIR);
}

/* Figure out hosts */

/* Fallback to default linked */
Expand All @@ -66,11 +116,19 @@
$verbose = [$_ENV['PMA_VERBOSE']];
$ports = [$_ENV['PMA_PORT']];
$ssls = [$_ENV['PMA_SSL']];
$ssl_verifies = [$_ENV['PMA_SSL_VERIFY']];
$ssl_cas = [$_ENV['PMA_SSL_CA']];
$ssl_keys = [$_ENV['PMA_SSL_KEY']];
$ssl_certs = [$_ENV['PMA_SSL_CERT']];
} elseif (! empty($_ENV['PMA_HOSTS'])) {
$hosts = array_map('trim', explode(',', $_ENV['PMA_HOSTS']));
$verbose = array_map('trim', explode(',', $_ENV['PMA_VERBOSES']));
$ports = array_map('trim', explode(',', $_ENV['PMA_PORTS']));
$ssls = array_map('trim', explode(',', $_ENV['PMA_SSLS']));
$ssl_verifies = array_map('trim', explode(',', $_ENV['PMA_SSL_VERIFIES']));
$ssl_cas = array_map('trim', explode(',', $_ENV['PMA_SSL_CAS']));
$ssl_keys = array_map('trim', explode(',', $_ENV['PMA_SSL_KEYS']));
$ssl_certs = array_map('trim', explode(',', $_ENV['PMA_SSL_CERTS']));
}

if (! empty($_ENV['PMA_SOCKET'])) {
Expand All @@ -84,6 +142,18 @@
if (isset($ssls[$i - 1]) && $ssls[$i - 1] === '1') {
$cfg['Servers'][$i]['ssl'] = $ssls[$i - 1];
}
if (isset($ssl_verifies[$i - 1]) && $ssl_verifies[$i - 1] === '1') {
$cfg['Servers'][$i]['ssl_verify'] = $ssl_verifies[$i - 1];
}
if (isset($ssl_cas[$i - 1])) {
$cfg['Servers'][$i]['ssl_ca'] = $ssl_cas[$i - 1];
}
if (isset($ssl_keys[$i - 1])) {
$cfg['Servers'][$i]['ssl_key'] = $ssl_keys[$i - 1];
}
if (isset($ssl_certs[$i - 1])) {
$cfg['Servers'][$i]['ssl_cert'] = $ssl_certs[$i - 1];
}
$cfg['Servers'][$i]['host'] = $hosts[$i - 1];
if (isset($verbose[$i - 1])) {
$cfg['Servers'][$i]['verbose'] = $verbose[$i - 1];
Expand Down
51 changes: 51 additions & 0 deletions apache/helpers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

/**
* Helper function to decode and save multiple SSL files from base64.
*
* @param string $base64FilesContents The base64 encoded string containing multiple files separated by commas.
* If no commas are present, the entire string is treated as a single file.
* @param string $prefix The prefix to use for the generated file names.
* @param string $extension The file extension to use for the generated files.
* @param string $storageFolder The folder where to store the generated files.
*
* @return string A comma-separated list of paths to the generated files.
*/
function decodeBase64AndSaveFiles(string $base64FilesContents, string $prefix, string $extension, string $storageFolder): string
{
// Ensure the output directory exists
if (! is_dir($storageFolder)) {
mkdir($storageFolder, 0755, true);
}

// Split the base64 string into an array of files
$base64FilesContents = explode(',', trim($base64FilesContents));
$counter = 1;
$outputFiles = [];

// Process each file
foreach ($base64FilesContents as $base64FileContent) {
$outputFile = $storageFolder . '/' . $prefix . '-' . $counter . '.' . $extension;

$fileContent = base64_decode($base64FileContent, true);
if ($fileContent === false) {
echo 'Failed to decode: ' . $base64FileContent;
exit(1);
}

// Write the decoded file to the output directory
if (file_put_contents($outputFile, $fileContent) === false) {
echo 'Failed to write to ' . $outputFile;
exit(1);
}

// Add the output file path to the list
$outputFiles[] = $outputFile;
$counter++;
}

// Return a comma-separated list of the generated file paths
return implode(',', $outputFiles);
}
72 changes: 71 additions & 1 deletion config.inc.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

require '/etc/phpmyadmin/config.secret.inc.php';
require_once '/etc/phpmyadmin/config.secret.inc.php';
require_once '/etc/phpmyadmin/helpers.php';

/* Ensure we got the environment */
$vars = [
Expand Down Expand Up @@ -29,6 +30,21 @@
'PMA_SAVEDIR',
'PMA_SSL',
'PMA_SSLS',
'PMA_SSL_DIR',
'PMA_SSL_VERIFY',
'PMA_SSL_VERIFIES',
'PMA_SSL_CA',
'PMA_SSL_CAS',
'PMA_SSL_CA_BASE64',
'PMA_SSL_CAS_BASE64',
'PMA_SSL_KEY',
'PMA_SSL_KEYS',
'PMA_SSL_KEY_BASE64',
'PMA_SSL_KEYS_BASE64',
'PMA_SSL_CERT',
'PMA_SSL_CERTS',
'PMA_SSL_CERT_BASE64',
'PMA_SSL_CERTS_BASE64',
];

foreach ($vars as $var) {
Expand All @@ -37,6 +53,11 @@
$_ENV[$var] = $env;
}
}

if (! defined('PMA_SSL_DIR')) {
define('PMA_SSL_DIR', $_ENV['PMA_SSL_DIR'] ?? '/etc/phpmyadmin/ssl');
}

if (isset($_ENV['PMA_QUERYHISTORYDB'])) {
$cfg['QueryHistoryDB'] = (bool) $_ENV['PMA_QUERYHISTORYDB'];
}
Expand All @@ -55,6 +76,35 @@
$cfg['PmaAbsoluteUri'] = trim($_ENV['PMA_ABSOLUTE_URI']);
}

if (isset($_ENV['PMA_SSL_CA_BASE64'])) {
$_ENV['PMA_SSL_CA'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CA_BASE64'], 'phpmyadmin-ssl-CA', 'pem', PMA_SSL_DIR);
}

/* Decode and save the SSL key from base64 */
if (isset($_ENV['PMA_SSL_KEY_BASE64'])) {
$_ENV['PMA_SSL_KEY'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_KEY_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
}

/* Decode and save the SSL certificate from base64 */
if (isset($_ENV['PMA_SSL_CERT_BASE64'])) {
$_ENV['PMA_SSL_CERT'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CERT_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
}

/* Decode and save multiple SSL CA certificates from base64 */
if (isset($_ENV['PMA_SSL_CAS_BASE64'])) {
$_ENV['PMA_SSL_CAS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CAS_BASE64'], 'phpmyadmin-ssl-CA', 'pem', PMA_SSL_DIR);
}

/* Decode and save multiple SSL keys from base64 */
if (isset($_ENV['PMA_SSL_KEYS_BASE64'])) {
$_ENV['PMA_SSL_KEYS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_KEYS_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
}

/* Decode and save multiple SSL certificates from base64 */
if (isset($_ENV['PMA_SSL_CERTS_BASE64'])) {
$_ENV['PMA_SSL_CERTS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CERTS_BASE64'], 'phpmyadmin-ssl-KEY', 'key', PMA_SSL_DIR);
}

/* Figure out hosts */

/* Fallback to default linked */
Expand All @@ -66,11 +116,19 @@
$verbose = [$_ENV['PMA_VERBOSE']];
$ports = [$_ENV['PMA_PORT']];
$ssls = [$_ENV['PMA_SSL']];
$ssl_verifies = [$_ENV['PMA_SSL_VERIFY']];
$ssl_cas = [$_ENV['PMA_SSL_CA']];
$ssl_keys = [$_ENV['PMA_SSL_KEY']];
$ssl_certs = [$_ENV['PMA_SSL_CERT']];
} elseif (! empty($_ENV['PMA_HOSTS'])) {
$hosts = array_map('trim', explode(',', $_ENV['PMA_HOSTS']));
$verbose = array_map('trim', explode(',', $_ENV['PMA_VERBOSES']));
$ports = array_map('trim', explode(',', $_ENV['PMA_PORTS']));
$ssls = array_map('trim', explode(',', $_ENV['PMA_SSLS']));
$ssl_verifies = array_map('trim', explode(',', $_ENV['PMA_SSL_VERIFIES']));
$ssl_cas = array_map('trim', explode(',', $_ENV['PMA_SSL_CAS']));
$ssl_keys = array_map('trim', explode(',', $_ENV['PMA_SSL_KEYS']));
$ssl_certs = array_map('trim', explode(',', $_ENV['PMA_SSL_CERTS']));
}

if (! empty($_ENV['PMA_SOCKET'])) {
Expand All @@ -84,6 +142,18 @@
if (isset($ssls[$i - 1]) && $ssls[$i - 1] === '1') {
$cfg['Servers'][$i]['ssl'] = $ssls[$i - 1];
}
if (isset($ssl_verifies[$i - 1]) && $ssl_verifies[$i - 1] === '1') {
$cfg['Servers'][$i]['ssl_verify'] = $ssl_verifies[$i - 1];
}
if (isset($ssl_cas[$i - 1])) {
$cfg['Servers'][$i]['ssl_ca'] = $ssl_cas[$i - 1];
}
if (isset($ssl_keys[$i - 1])) {
$cfg['Servers'][$i]['ssl_key'] = $ssl_keys[$i - 1];
}
if (isset($ssl_certs[$i - 1])) {
$cfg['Servers'][$i]['ssl_cert'] = $ssl_certs[$i - 1];
}
$cfg['Servers'][$i]['host'] = $hosts[$i - 1];
if (isset($verbose[$i - 1])) {
$cfg['Servers'][$i]['verbose'] = $verbose[$i - 1];
Expand Down
Loading

0 comments on commit e1fe998

Please sign in to comment.