From fc2b5ff83b4bc6f19b102d2be0504db51161870f Mon Sep 17 00:00:00 2001 From: demeritcowboy Date: Wed, 26 Aug 2020 17:26:54 -0400 Subject: [PATCH] autodetect ssl --- setup/plugins/init/Backdrop.civi-setup.php | 2 + setup/plugins/init/Drupal.civi-setup.php | 2 + setup/plugins/init/Drupal8.civi-setup.php | 2 + .../InstallSettingsFile.civi-setup.php | 7 + setup/src/Setup/DrupalUtil.php | 54 +++++++ .../CRM/common/civicrm.settings.php.template | 2 +- tests/phpunit/Civi/Setup/DrupalUtilTest.php | 133 ++++++++++++++++++ 7 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/Civi/Setup/DrupalUtilTest.php diff --git a/setup/plugins/init/Backdrop.civi-setup.php b/setup/plugins/init/Backdrop.civi-setup.php index 57e867f90c..2320b88dac 100644 --- a/setup/plugins/init/Backdrop.civi-setup.php +++ b/setup/plugins/init/Backdrop.civi-setup.php @@ -39,11 +39,13 @@ if (!defined('CIVI_SETUP')) { // Compute DSN. global $databases; + $ssl_params = \Civi\Setup\DrupalUtil::guessSslParams($databases['default']['default']); $model->db = $model->cmsDb = array( 'server' => \Civi\Setup\DbUtil::encodeHostPort($databases['default']['default']['host'], $databases['default']['default']['port'] ?: NULL), 'username' => $databases['default']['default']['username'], 'password' => $databases['default']['default']['password'], 'database' => $databases['default']['default']['database'], + 'ssl_params' => empty($ssl_params) ? NULL : $ssl_params, ); // Compute URLs diff --git a/setup/plugins/init/Drupal.civi-setup.php b/setup/plugins/init/Drupal.civi-setup.php index b24d7c164c..d57ff432c2 100644 --- a/setup/plugins/init/Drupal.civi-setup.php +++ b/setup/plugins/init/Drupal.civi-setup.php @@ -37,11 +37,13 @@ if (!defined('CIVI_SETUP')) { // Compute DSN. global $databases; + $ssl_params = \Civi\Setup\DrupalUtil::guessSslParams($databases['default']['default']); $model->db = $model->cmsDb = array( 'server' => \Civi\Setup\DbUtil::encodeHostPort($databases['default']['default']['host'], $databases['default']['default']['port'] ?: NULL), 'username' => $databases['default']['default']['username'], 'password' => $databases['default']['default']['password'], 'database' => $databases['default']['default']['database'], + 'ssl_params' => empty($ssl_params) ? NULL : $ssl_params, ); // Compute cmsBaseUrl. diff --git a/setup/plugins/init/Drupal8.civi-setup.php b/setup/plugins/init/Drupal8.civi-setup.php index a693cba92c..22034e9089 100644 --- a/setup/plugins/init/Drupal8.civi-setup.php +++ b/setup/plugins/init/Drupal8.civi-setup.php @@ -40,11 +40,13 @@ if (!defined('CIVI_SETUP')) { // Compute DSN. $connectionOptions = \Drupal::database()->getConnectionOptions(); + $ssl_params = \Civi\Setup\DrupalUtil::guessSslParams($connectionOptions); $model->db = $model->cmsDb = array( 'server' => \Civi\Setup\DbUtil::encodeHostPort($connectionOptions['host'], $connectionOptions['port'] ?: NULL), 'username' => $connectionOptions['username'], 'password' => $connectionOptions['password'], 'database' => $connectionOptions['database'], + 'ssl_params' => empty($ssl_params) ? NULL : $ssl_params, ); // Compute cmsBaseUrl. diff --git a/setup/plugins/installFiles/InstallSettingsFile.civi-setup.php b/setup/plugins/installFiles/InstallSettingsFile.civi-setup.php index 29aedb2b07..5422292f32 100644 --- a/setup/plugins/installFiles/InstallSettingsFile.civi-setup.php +++ b/setup/plugins/installFiles/InstallSettingsFile.civi-setup.php @@ -71,6 +71,13 @@ if (!defined('CIVI_SETUP')) { $params['CMSdbPass'] = addslashes($m->cmsDb['password']); $params['CMSdbHost'] = addslashes($m->cmsDb['server']); $params['CMSdbName'] = addslashes($m->cmsDb['database']); + // The '&' prefix is awkward, but we don't know what's already in the file. + // At the time of writing, it has ?new_link=true. If that is removed, + // then need to update this. + // The PHP_QUERY_RFC3986 is important because PEAR::DB will interpret plus + // signs as a reference to its old DSN format and mangle the DSN, so we + // need to use %20 for spaces. + $params['CMSdbSSL'] = empty($m->cmsDb['ssl_params']) ? '' : addslashes('&' . http_build_query($m->cmsDb['ssl_params'], '', '&', PHP_QUERY_RFC3986)); $params['siteKey'] = addslashes($m->siteKey); $extraSettings = array(); diff --git a/setup/src/Setup/DrupalUtil.php b/setup/src/Setup/DrupalUtil.php index 0f5ab95205..8d1e95bc2e 100644 --- a/setup/src/Setup/DrupalUtil.php +++ b/setup/src/Setup/DrupalUtil.php @@ -71,4 +71,58 @@ class DrupalUtil { */ } + /** + * Guess if the CMS is using SSL for MySQL and what the corresponding + * parameters should be for PEAR::DB. + * + * Not all combinations will work. See the install docs for a list of known + * configurations that do. We don't enforce that here since we're just + * trying to guess a default based on what they already have. + * + * @param array $cmsDatabaseParams + * The contents of the section from drupal's settings.php where it defines + * the $database array, usually under 'default'. + * @return array + * The corresponding guessed params for PEAR::DB. + */ + public static function guessSslParams(array $cmsDatabaseParams):array { + // If the pdo-mysql extension isn't loaded or they have nothing in drupal + // config for pdo, then we're done. PDO isn't required for Civi, but note + // the references to PDO constants below would fail and they obviously + // wouldn't have them in drupal config then. + if (empty($cmsDatabaseParams['pdo']) || !extension_loaded('pdo_mysql')) { + return []; + } + + $pdo = $cmsDatabaseParams['pdo']; + + $pdo_map = [ + \PDO::MYSQL_ATTR_SSL_CA => 'ca', + \PDO::MYSQL_ATTR_SSL_KEY => 'key', + \PDO::MYSQL_ATTR_SSL_CERT => 'cert', + \PDO::MYSQL_ATTR_SSL_CAPATH => 'capath', + \PDO::MYSQL_ATTR_SSL_CIPHER => 'cipher', + ]; + + $ssl_params = []; + + // If they have one set in drupal config and it's a string, then copy + // it over verbatim. + foreach ($pdo_map as $pdo_name => $ssl_name) { + if (!empty($pdo[$pdo_name]) && is_string($pdo[$pdo_name])) { + $ssl_params[$ssl_name] = $pdo[$pdo_name]; + } + } + + // No client certificate or server verification, but want SSL. Return our + // made-up indicator ssl=1 that isn't a real mysqli option but which we + // recognize. It's possible they have other params set too which we pass + // along from above, but that may not be compatible but it's up to them. + if (($pdo[\PDO::MYSQL_ATTR_SSL_CA] ?? NULL) === TRUE && ($pdo[\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] ?? NULL) === FALSE) { + $ssl_params['ssl'] = 1; + } + + return $ssl_params; + } + } diff --git a/templates/CRM/common/civicrm.settings.php.template b/templates/CRM/common/civicrm.settings.php.template index 3d0575569f..d66a63d632 100644 --- a/templates/CRM/common/civicrm.settings.php.template +++ b/templates/CRM/common/civicrm.settings.php.template @@ -73,7 +73,7 @@ if (!defined('CIVICRM_UF')) { * define( 'CIVICRM_UF_DSN', 'mysql://cms_db_username:cms_db_password@db_server/cms_database?new_link=true'); */ if (!defined('CIVICRM_UF_DSN') && CIVICRM_UF !== 'UnitTests') { - define( 'CIVICRM_UF_DSN' , 'mysql://%%CMSdbUser%%:%%CMSdbPass%%@%%CMSdbHost%%/%%CMSdbName%%?new_link=true'); + define( 'CIVICRM_UF_DSN' , 'mysql://%%CMSdbUser%%:%%CMSdbPass%%@%%CMSdbHost%%/%%CMSdbName%%?new_link=true%%CMSdbSSL%%'); } // %%extraSettings%% diff --git a/tests/phpunit/Civi/Setup/DrupalUtilTest.php b/tests/phpunit/Civi/Setup/DrupalUtilTest.php new file mode 100644 index 0000000000..adf606b04f --- /dev/null +++ b/tests/phpunit/Civi/Setup/DrupalUtilTest.php @@ -0,0 +1,133 @@ +assertSame($expected, \Civi\Setup\DrupalUtil::guessSslParams($input)); + } + + /** + * Data provider for testGuessSslParams + * @return array + */ + public function pdoParamsProvider():array { + return [ + 'empty' => [[], []], + 'empty2' => [['pdo' => []], []], + 'no client certificate, no server verification' => [ + [ + 'pdo' => [ + \PDO::MYSQL_ATTR_SSL_CA => TRUE, + \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => FALSE, + ], + ], + ['ssl' => 1], + ], + 'typical client certificate with server verification' => [ + [ + 'pdo' => [ + \PDO::MYSQL_ATTR_SSL_CA => '/tmp/cacert.crt', + \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key', + \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt', + ], + ], + [ + 'ca' => '/tmp/cacert.crt', + 'key' => '/tmp/my.key', + 'cert' => '/tmp/cert.crt', + ], + ], + 'client certificate, no server verification' => [ + [ + 'pdo' => [ + \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => FALSE, + \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key', + \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt', + ], + ], + [ + 'key' => '/tmp/my.key', + 'cert' => '/tmp/cert.crt', + ], + ], + 'self-signed client certificate with server verification' => [ + [ + 'pdo' => [ + \PDO::MYSQL_ATTR_SSL_CA => '/tmp/cert.crt', + \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key', + \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt', + ], + ], + [ + 'ca' => '/tmp/cert.crt', + 'key' => '/tmp/my.key', + 'cert' => '/tmp/cert.crt', + ], + ], + 'Not sure what would happen in practice but is all the string params' => [ + [ + 'pdo' => [ + \PDO::MYSQL_ATTR_SSL_CA => '/tmp/cacert.crt', + \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key', + \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt', + \PDO::MYSQL_ATTR_SSL_CAPATH => '/tmp/cacert_folder', + \PDO::MYSQL_ATTR_SSL_CIPHER => 'aes', + ], + ], + [ + 'ca' => '/tmp/cacert.crt', + 'key' => '/tmp/my.key', + 'cert' => '/tmp/cert.crt', + 'capath' => '/tmp/cacert_folder', + 'cipher' => 'aes', + ], + ], + 'Ditto, but showing extra ones should get ignored' => [ + [ + 'pdo' => [ + \PDO::MYSQL_ATTR_SSL_CA => '/tmp/cacert.crt', + \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key', + \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt', + \PDO::MYSQL_ATTR_SSL_CAPATH => '/tmp/cacert_folder', + \PDO::MYSQL_ATTR_SSL_CIPHER => 'aes', + 'fourteen' => 'the number fourteen', + ], + ], + [ + 'ca' => '/tmp/cacert.crt', + 'key' => '/tmp/my.key', + 'cert' => '/tmp/cert.crt', + 'capath' => '/tmp/cacert_folder', + 'cipher' => 'aes', + ], + ], + "some windows paths shouldn't get mangled" => [ + [ + 'pdo' => [ + \PDO::MYSQL_ATTR_SSL_CA => 'C:/Program Files/MariaDB 10.3/data/cacert.crt', + \PDO::MYSQL_ATTR_SSL_KEY => 'C:/Program Files/MariaDB 10.3/data/my.key', + \PDO::MYSQL_ATTR_SSL_CERT => 'C:\\Program Files\\MariaDB 10.3\\data\\cert.crt', + ], + ], + [ + 'ca' => 'C:/Program Files/MariaDB 10.3/data/cacert.crt', + 'key' => 'C:/Program Files/MariaDB 10.3/data/my.key', + 'cert' => 'C:\\Program Files\\MariaDB 10.3\\data\\cert.crt', + ], + ], + ]; + } + +} -- 2.25.1