Skip to content

Commit e1fe998

Browse files
Add mutual TLS (mTLS) support for remote database connections in PhpMyAdmin (#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]>
1 parent 12b0f34 commit e1fe998

16 files changed

+534
-6
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ __pycache__
33
.pytest_cache
44
.vscode
55
.history
6-
.venv
6+
.venv
7+
testing/*.pem

Dockerfile-alpine.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ RUN set -ex; \
3939

4040
# set recommended PHP.ini settings
4141
# see https://secure.php.net/manual/en/opcache.installation.php
42+
ENV PMA_SSL_DIR /etc/phpmyadmin/ssl
4243
ENV MAX_EXECUTION_TIME 600
4344
ENV MEMORY_LIMIT 512M
4445
ENV UPLOAD_LIMIT 2048K
@@ -91,8 +92,11 @@ RUN set -ex; \
9192
gnupg \
9293
; \
9394
mkdir $SESSION_SAVE_PATH; \
95+
mkdir -p $PMA_SSL_DIR; \
9496
chmod 1777 $SESSION_SAVE_PATH; \
97+
chmod 755 $PMA_SSL_DIR; \
9598
chown www-data:www-data $SESSION_SAVE_PATH; \
99+
chown www-data:www-data $PMA_SSL_DIR; \
96100
\
97101
export GNUPGHOME="$(mktemp -d)"; \
98102
export GPGKEY="%%GPG_KEY%%"; \

Dockerfile-debian.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ RUN set -ex; \
5050

5151
# set recommended PHP.ini settings
5252
# see https://secure.php.net/manual/en/opcache.installation.php
53+
ENV PMA_SSL_DIR /etc/phpmyadmin/ssl
5354
ENV MAX_EXECUTION_TIME 600
5455
ENV MEMORY_LIMIT 512M
5556
ENV UPLOAD_LIMIT 2048K
@@ -107,8 +108,11 @@ RUN set -ex; \
107108
dirmngr \
108109
; \
109110
mkdir $SESSION_SAVE_PATH; \
111+
mkdir -p $PMA_SSL_DIR; \
110112
chmod 1777 $SESSION_SAVE_PATH; \
113+
chmod 755 $PMA_SSL_DIR; \
111114
chown www-data:www-data $SESSION_SAVE_PATH; \
115+
chown www-data:www-data $PMA_SSL_DIR; \
112116
\
113117
export GNUPGHOME="$(mktemp -d)"; \
114118
export GPGKEY="%%GPG_KEY%%"; \

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,17 @@ docker run --name phpmyadmin -d -e PMA_HOSTS='sslhost,nosslhost' -e PMA_SSLS='1,
184184
* ``PMA_PORTS`` - define comma separated list of ports of the MySQL servers
185185
* ``PMA_SOCKET`` - define socket file for the MySQL connection
186186
* ``PMA_SOCKETS`` - define comma separated list of socket files for the MySQL connections
187+
* ``PMA_SSL_DIR`` - define the path used for SSL files generated from environement variables, default value is `/etc/phpmyadmin/ssl`
187188
* ``PMA_SSL`` - when set to 1, defines SSL usage for the MySQL connection
188189
* ``PMA_SSLS`` - comma separated list of `0` and `1` defining SSL usage for the corresponding MySQL connections
190+
* ``PMA_SSL_VERIFY`` - when set to 1, enables SSL certificate verification for the MySQL connection.
191+
* ``PMA_SSL_VERIFIES`` - comma-separated list of `0` and `1` to enable or disable SSL certificate verification for multiple MySQL connections.
192+
* ``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`.
193+
* ``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`.
194+
* ``PMA_SSL_CERT`` - in the context of mutual TLS security, allows setting your certificate file as a string inside the default `config.inc.php`.
195+
* ``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`.
196+
* ``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`.
197+
* ``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`.
189198
* ``PMA_USER`` and ``PMA_PASSWORD`` - define username and password to use only with the `config` authentication method
190199
* ``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).
191200
* ``PMA_CONFIG_BASE64`` - if set, this option will override the default `config.inc.php` with the base64 decoded contents of the variable
@@ -212,6 +221,19 @@ For usage with Docker secrets, appending ``_FILE`` to the ``PMA_PASSWORD`` envir
212221
docker run --name phpmyadmin -d -e PMA_PASSWORD_FILE=/run/secrets/db_password.txt -p 8080:80 phpmyadmin:latest
213222
```
214223

224+
#### Variables that can store the file contents using ``_BASE64``
225+
226+
- `PMA_SSL_CA`
227+
- `PMA_SSL_CAS`
228+
- `PMA_SSL_KEY`
229+
- `PMA_SSL_KEYS`
230+
- `PMA_SSL_CERT`
231+
- `PMA_SSL_CERTS`
232+
233+
Also includes: `PMA_CONFIG_BASE64` or `PMA_USER_CONFIG_BASE64`.
234+
235+
For example, the variable would be named `PMA_SSL_CA_BASE64` and the value is the base64 encoded contents of the file.
236+
215237
#### Variables that can be read from a file using ``_FILE``
216238

217239
- `MYSQL_ROOT_PASSWORD`

apache/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ RUN set -ex; \
5151

5252
# set recommended PHP.ini settings
5353
# see https://secure.php.net/manual/en/opcache.installation.php
54+
ENV PMA_SSL_DIR /etc/phpmyadmin/ssl
5455
ENV MAX_EXECUTION_TIME 600
5556
ENV MEMORY_LIMIT 512M
5657
ENV UPLOAD_LIMIT 2048K
@@ -108,8 +109,11 @@ RUN set -ex; \
108109
dirmngr \
109110
; \
110111
mkdir $SESSION_SAVE_PATH; \
112+
mkdir -p $PMA_SSL_DIR; \
111113
chmod 1777 $SESSION_SAVE_PATH; \
114+
chmod 755 $PMA_SSL_DIR; \
112115
chown www-data:www-data $SESSION_SAVE_PATH; \
116+
chown www-data:www-data $PMA_SSL_DIR; \
113117
\
114118
export GNUPGHOME="$(mktemp -d)"; \
115119
export GPGKEY="3D06A59ECE730EB71B511C17CE752F178259BD92"; \

apache/config.inc.php

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

3-
require '/etc/phpmyadmin/config.secret.inc.php';
3+
require_once '/etc/phpmyadmin/config.secret.inc.php';
4+
require_once '/etc/phpmyadmin/helpers.php';
45

56
/* Ensure we got the environment */
67
$vars = [
@@ -29,6 +30,21 @@
2930
'PMA_SAVEDIR',
3031
'PMA_SSL',
3132
'PMA_SSLS',
33+
'PMA_SSL_DIR',
34+
'PMA_SSL_VERIFY',
35+
'PMA_SSL_VERIFIES',
36+
'PMA_SSL_CA',
37+
'PMA_SSL_CAS',
38+
'PMA_SSL_CA_BASE64',
39+
'PMA_SSL_CAS_BASE64',
40+
'PMA_SSL_KEY',
41+
'PMA_SSL_KEYS',
42+
'PMA_SSL_KEY_BASE64',
43+
'PMA_SSL_KEYS_BASE64',
44+
'PMA_SSL_CERT',
45+
'PMA_SSL_CERTS',
46+
'PMA_SSL_CERT_BASE64',
47+
'PMA_SSL_CERTS_BASE64',
3248
];
3349

3450
foreach ($vars as $var) {
@@ -37,6 +53,11 @@
3753
$_ENV[$var] = $env;
3854
}
3955
}
56+
57+
if (! defined('PMA_SSL_DIR')) {
58+
define('PMA_SSL_DIR', $_ENV['PMA_SSL_DIR'] ?? '/etc/phpmyadmin/ssl');
59+
}
60+
4061
if (isset($_ENV['PMA_QUERYHISTORYDB'])) {
4162
$cfg['QueryHistoryDB'] = (bool) $_ENV['PMA_QUERYHISTORYDB'];
4263
}
@@ -55,6 +76,35 @@
5576
$cfg['PmaAbsoluteUri'] = trim($_ENV['PMA_ABSOLUTE_URI']);
5677
}
5778

79+
if (isset($_ENV['PMA_SSL_CA_BASE64'])) {
80+
$_ENV['PMA_SSL_CA'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CA_BASE64'], 'phpmyadmin-ssl-CA', 'pem', PMA_SSL_DIR);
81+
}
82+
83+
/* Decode and save the SSL key from base64 */
84+
if (isset($_ENV['PMA_SSL_KEY_BASE64'])) {
85+
$_ENV['PMA_SSL_KEY'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_KEY_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
86+
}
87+
88+
/* Decode and save the SSL certificate from base64 */
89+
if (isset($_ENV['PMA_SSL_CERT_BASE64'])) {
90+
$_ENV['PMA_SSL_CERT'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CERT_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
91+
}
92+
93+
/* Decode and save multiple SSL CA certificates from base64 */
94+
if (isset($_ENV['PMA_SSL_CAS_BASE64'])) {
95+
$_ENV['PMA_SSL_CAS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CAS_BASE64'], 'phpmyadmin-ssl-CA', 'pem', PMA_SSL_DIR);
96+
}
97+
98+
/* Decode and save multiple SSL keys from base64 */
99+
if (isset($_ENV['PMA_SSL_KEYS_BASE64'])) {
100+
$_ENV['PMA_SSL_KEYS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_KEYS_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
101+
}
102+
103+
/* Decode and save multiple SSL certificates from base64 */
104+
if (isset($_ENV['PMA_SSL_CERTS_BASE64'])) {
105+
$_ENV['PMA_SSL_CERTS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CERTS_BASE64'], 'phpmyadmin-ssl-KEY', 'key', PMA_SSL_DIR);
106+
}
107+
58108
/* Figure out hosts */
59109

60110
/* Fallback to default linked */
@@ -66,11 +116,19 @@
66116
$verbose = [$_ENV['PMA_VERBOSE']];
67117
$ports = [$_ENV['PMA_PORT']];
68118
$ssls = [$_ENV['PMA_SSL']];
119+
$ssl_verifies = [$_ENV['PMA_SSL_VERIFY']];
120+
$ssl_cas = [$_ENV['PMA_SSL_CA']];
121+
$ssl_keys = [$_ENV['PMA_SSL_KEY']];
122+
$ssl_certs = [$_ENV['PMA_SSL_CERT']];
69123
} elseif (! empty($_ENV['PMA_HOSTS'])) {
70124
$hosts = array_map('trim', explode(',', $_ENV['PMA_HOSTS']));
71125
$verbose = array_map('trim', explode(',', $_ENV['PMA_VERBOSES']));
72126
$ports = array_map('trim', explode(',', $_ENV['PMA_PORTS']));
73127
$ssls = array_map('trim', explode(',', $_ENV['PMA_SSLS']));
128+
$ssl_verifies = array_map('trim', explode(',', $_ENV['PMA_SSL_VERIFIES']));
129+
$ssl_cas = array_map('trim', explode(',', $_ENV['PMA_SSL_CAS']));
130+
$ssl_keys = array_map('trim', explode(',', $_ENV['PMA_SSL_KEYS']));
131+
$ssl_certs = array_map('trim', explode(',', $_ENV['PMA_SSL_CERTS']));
74132
}
75133

76134
if (! empty($_ENV['PMA_SOCKET'])) {
@@ -84,6 +142,18 @@
84142
if (isset($ssls[$i - 1]) && $ssls[$i - 1] === '1') {
85143
$cfg['Servers'][$i]['ssl'] = $ssls[$i - 1];
86144
}
145+
if (isset($ssl_verifies[$i - 1]) && $ssl_verifies[$i - 1] === '1') {
146+
$cfg['Servers'][$i]['ssl_verify'] = $ssl_verifies[$i - 1];
147+
}
148+
if (isset($ssl_cas[$i - 1])) {
149+
$cfg['Servers'][$i]['ssl_ca'] = $ssl_cas[$i - 1];
150+
}
151+
if (isset($ssl_keys[$i - 1])) {
152+
$cfg['Servers'][$i]['ssl_key'] = $ssl_keys[$i - 1];
153+
}
154+
if (isset($ssl_certs[$i - 1])) {
155+
$cfg['Servers'][$i]['ssl_cert'] = $ssl_certs[$i - 1];
156+
}
87157
$cfg['Servers'][$i]['host'] = $hosts[$i - 1];
88158
if (isset($verbose[$i - 1])) {
89159
$cfg['Servers'][$i]['verbose'] = $verbose[$i - 1];

apache/helpers.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Helper function to decode and save multiple SSL files from base64.
7+
*
8+
* @param string $base64FilesContents The base64 encoded string containing multiple files separated by commas.
9+
* If no commas are present, the entire string is treated as a single file.
10+
* @param string $prefix The prefix to use for the generated file names.
11+
* @param string $extension The file extension to use for the generated files.
12+
* @param string $storageFolder The folder where to store the generated files.
13+
*
14+
* @return string A comma-separated list of paths to the generated files.
15+
*/
16+
function decodeBase64AndSaveFiles(string $base64FilesContents, string $prefix, string $extension, string $storageFolder): string
17+
{
18+
// Ensure the output directory exists
19+
if (! is_dir($storageFolder)) {
20+
mkdir($storageFolder, 0755, true);
21+
}
22+
23+
// Split the base64 string into an array of files
24+
$base64FilesContents = explode(',', trim($base64FilesContents));
25+
$counter = 1;
26+
$outputFiles = [];
27+
28+
// Process each file
29+
foreach ($base64FilesContents as $base64FileContent) {
30+
$outputFile = $storageFolder . '/' . $prefix . '-' . $counter . '.' . $extension;
31+
32+
$fileContent = base64_decode($base64FileContent, true);
33+
if ($fileContent === false) {
34+
echo 'Failed to decode: ' . $base64FileContent;
35+
exit(1);
36+
}
37+
38+
// Write the decoded file to the output directory
39+
if (file_put_contents($outputFile, $fileContent) === false) {
40+
echo 'Failed to write to ' . $outputFile;
41+
exit(1);
42+
}
43+
44+
// Add the output file path to the list
45+
$outputFiles[] = $outputFile;
46+
$counter++;
47+
}
48+
49+
// Return a comma-separated list of the generated file paths
50+
return implode(',', $outputFiles);
51+
}

config.inc.php

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

3-
require '/etc/phpmyadmin/config.secret.inc.php';
3+
require_once '/etc/phpmyadmin/config.secret.inc.php';
4+
require_once '/etc/phpmyadmin/helpers.php';
45

56
/* Ensure we got the environment */
67
$vars = [
@@ -29,6 +30,21 @@
2930
'PMA_SAVEDIR',
3031
'PMA_SSL',
3132
'PMA_SSLS',
33+
'PMA_SSL_DIR',
34+
'PMA_SSL_VERIFY',
35+
'PMA_SSL_VERIFIES',
36+
'PMA_SSL_CA',
37+
'PMA_SSL_CAS',
38+
'PMA_SSL_CA_BASE64',
39+
'PMA_SSL_CAS_BASE64',
40+
'PMA_SSL_KEY',
41+
'PMA_SSL_KEYS',
42+
'PMA_SSL_KEY_BASE64',
43+
'PMA_SSL_KEYS_BASE64',
44+
'PMA_SSL_CERT',
45+
'PMA_SSL_CERTS',
46+
'PMA_SSL_CERT_BASE64',
47+
'PMA_SSL_CERTS_BASE64',
3248
];
3349

3450
foreach ($vars as $var) {
@@ -37,6 +53,11 @@
3753
$_ENV[$var] = $env;
3854
}
3955
}
56+
57+
if (! defined('PMA_SSL_DIR')) {
58+
define('PMA_SSL_DIR', $_ENV['PMA_SSL_DIR'] ?? '/etc/phpmyadmin/ssl');
59+
}
60+
4061
if (isset($_ENV['PMA_QUERYHISTORYDB'])) {
4162
$cfg['QueryHistoryDB'] = (bool) $_ENV['PMA_QUERYHISTORYDB'];
4263
}
@@ -55,6 +76,35 @@
5576
$cfg['PmaAbsoluteUri'] = trim($_ENV['PMA_ABSOLUTE_URI']);
5677
}
5778

79+
if (isset($_ENV['PMA_SSL_CA_BASE64'])) {
80+
$_ENV['PMA_SSL_CA'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CA_BASE64'], 'phpmyadmin-ssl-CA', 'pem', PMA_SSL_DIR);
81+
}
82+
83+
/* Decode and save the SSL key from base64 */
84+
if (isset($_ENV['PMA_SSL_KEY_BASE64'])) {
85+
$_ENV['PMA_SSL_KEY'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_KEY_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
86+
}
87+
88+
/* Decode and save the SSL certificate from base64 */
89+
if (isset($_ENV['PMA_SSL_CERT_BASE64'])) {
90+
$_ENV['PMA_SSL_CERT'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CERT_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
91+
}
92+
93+
/* Decode and save multiple SSL CA certificates from base64 */
94+
if (isset($_ENV['PMA_SSL_CAS_BASE64'])) {
95+
$_ENV['PMA_SSL_CAS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CAS_BASE64'], 'phpmyadmin-ssl-CA', 'pem', PMA_SSL_DIR);
96+
}
97+
98+
/* Decode and save multiple SSL keys from base64 */
99+
if (isset($_ENV['PMA_SSL_KEYS_BASE64'])) {
100+
$_ENV['PMA_SSL_KEYS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_KEYS_BASE64'], 'phpmyadmin-ssl-CERT', 'cert', PMA_SSL_DIR);
101+
}
102+
103+
/* Decode and save multiple SSL certificates from base64 */
104+
if (isset($_ENV['PMA_SSL_CERTS_BASE64'])) {
105+
$_ENV['PMA_SSL_CERTS'] = decodeBase64AndSaveFiles($_ENV['PMA_SSL_CERTS_BASE64'], 'phpmyadmin-ssl-KEY', 'key', PMA_SSL_DIR);
106+
}
107+
58108
/* Figure out hosts */
59109

60110
/* Fallback to default linked */
@@ -66,11 +116,19 @@
66116
$verbose = [$_ENV['PMA_VERBOSE']];
67117
$ports = [$_ENV['PMA_PORT']];
68118
$ssls = [$_ENV['PMA_SSL']];
119+
$ssl_verifies = [$_ENV['PMA_SSL_VERIFY']];
120+
$ssl_cas = [$_ENV['PMA_SSL_CA']];
121+
$ssl_keys = [$_ENV['PMA_SSL_KEY']];
122+
$ssl_certs = [$_ENV['PMA_SSL_CERT']];
69123
} elseif (! empty($_ENV['PMA_HOSTS'])) {
70124
$hosts = array_map('trim', explode(',', $_ENV['PMA_HOSTS']));
71125
$verbose = array_map('trim', explode(',', $_ENV['PMA_VERBOSES']));
72126
$ports = array_map('trim', explode(',', $_ENV['PMA_PORTS']));
73127
$ssls = array_map('trim', explode(',', $_ENV['PMA_SSLS']));
128+
$ssl_verifies = array_map('trim', explode(',', $_ENV['PMA_SSL_VERIFIES']));
129+
$ssl_cas = array_map('trim', explode(',', $_ENV['PMA_SSL_CAS']));
130+
$ssl_keys = array_map('trim', explode(',', $_ENV['PMA_SSL_KEYS']));
131+
$ssl_certs = array_map('trim', explode(',', $_ENV['PMA_SSL_CERTS']));
74132
}
75133

76134
if (! empty($_ENV['PMA_SOCKET'])) {
@@ -84,6 +142,18 @@
84142
if (isset($ssls[$i - 1]) && $ssls[$i - 1] === '1') {
85143
$cfg['Servers'][$i]['ssl'] = $ssls[$i - 1];
86144
}
145+
if (isset($ssl_verifies[$i - 1]) && $ssl_verifies[$i - 1] === '1') {
146+
$cfg['Servers'][$i]['ssl_verify'] = $ssl_verifies[$i - 1];
147+
}
148+
if (isset($ssl_cas[$i - 1])) {
149+
$cfg['Servers'][$i]['ssl_ca'] = $ssl_cas[$i - 1];
150+
}
151+
if (isset($ssl_keys[$i - 1])) {
152+
$cfg['Servers'][$i]['ssl_key'] = $ssl_keys[$i - 1];
153+
}
154+
if (isset($ssl_certs[$i - 1])) {
155+
$cfg['Servers'][$i]['ssl_cert'] = $ssl_certs[$i - 1];
156+
}
87157
$cfg['Servers'][$i]['host'] = $hosts[$i - 1];
88158
if (isset($verbose[$i - 1])) {
89159
$cfg['Servers'][$i]['verbose'] = $verbose[$i - 1];

0 commit comments

Comments
 (0)