Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6214417
feat:(config.inc.php/docker-entrypoint.sh): Add support for mTLS to a…
LordRobinCbz Dec 11, 2024
2ee310d
Merge pull request #1 from LordRobinCbz/develop
LordRobinCbz Dec 11, 2024
b78da1f
fix(config.inc.php/docker-entrypoint.sh,dockerfile,helpers.php): Move…
LordRobinCbz Dec 21, 2024
a284aff
Merge pull request #2 from LordRobinCbz/develop
LordRobinCbz Dec 21, 2024
088137e
fix(config.inc.php/docker-entrypoint.sh,dockerfile,helpers.php): Move…
LordRobinCbz Dec 21, 2024
0ffaad3
Merge pull request #3 from LordRobinCbz/develop
LordRobinCbz Dec 21, 2024
a245719
fix(config.inc.php): import require statements
LordRobinCbz Dec 21, 2024
76f4def
Update apache/helpers.php
LordRobinCbz Dec 21, 2024
92ca977
Update apache/helpers.php
LordRobinCbz Dec 21, 2024
35ad5ea
Update apache/helpers.php
LordRobinCbz Dec 21, 2024
0e85faf
fix(dockerfiles, config.inc.php): Add ENV in Dockerfile, edited templ…
LordRobinCbz Dec 21, 2024
b044109
fix(helpers,update.sh): add helpers file to the root and edited updat…
LordRobinCbz Dec 22, 2024
3332557
Fix return type hint and detect base64 decode crashes
williamdes Jan 3, 2025
4ceefa6
Apply the coding standard
williamdes Jan 3, 2025
95bf927
Make the helper function usable for another use
williamdes Jan 3, 2025
5e50958
Add chown to the ssl folder
williamdes Jan 3, 2025
9b2667c
Fixup config and edit the README
williamdes Jan 4, 2025
cdbcee1
Also mkdir the folder and chmod it
williamdes Jan 4, 2025
2cf099c
Ignore all pem files
williamdes Jan 4, 2025
518ebc0
mkdir all the path
williamdes Jan 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
williamdes marked this conversation as resolved.
Show resolved Hide resolved

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