diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php --- a/scripts/sql/manage_storage.php +++ b/scripts/sql/manage_storage.php @@ -148,12 +148,14 @@ $default_user = $ref->getUser(); $default_host = $ref->getHost(); $default_port = $ref->getPort(); + $default_use_tls = $ref->getUseTls(); $test_api = id(new PhabricatorStorageManagementAPI()) ->setUser($default_user) ->setHost($default_host) ->setPort($default_port) ->setPassword($ref->getPass()) + ->setUseTls($default_use_tls) ->setNamespace($args->getArg('namespace')); try { @@ -170,7 +172,8 @@ 'storage. Run these commands to set up credentials:'), " $ ./bin/config set mysql.host __host__\n". " $ ./bin/config set mysql.user __username__\n". - " $ ./bin/config set mysql.pass __password__", + " $ ./bin/config set mysql.pass __password__\n". + " $ ./bin/config set mysql.use-tls __use-tls__", pht( 'These standard credentials are separate from any administrative '. 'credentials provided to this command with __%s__ or '. @@ -202,6 +205,7 @@ ->setHost($default_host) ->setPort($default_port) ->setPassword($password) + ->setUseTls($default_use_tls) ->setNamespace($args->getArg('namespace')) ->setDisableUTF8MB4($args->getArg('disable-utf8mb4')); PhabricatorEnv::overrideConfig('mysql.user', $api->getUser()); diff --git a/src/applications/config/option/PhabricatorMySQLConfigOptions.php b/src/applications/config/option/PhabricatorMySQLConfigOptions.php --- a/src/applications/config/option/PhabricatorMySQLConfigOptions.php +++ b/src/applications/config/option/PhabricatorMySQLConfigOptions.php @@ -47,10 +47,14 @@ "this namespace if you want. Normally, you should not do this ". "unless you are developing extensions and using namespaces to ". "separate multiple sandbox datasets.")), - $this->newOption('mysql.port', 'string', null) + $this->newOption('mysql.port', 'string', null) ->setLocked(true) ->setDescription( pht('MySQL port to use when connecting to the database.')), + $this->newOption('mysql.use-tls', 'bool', false) + ->setLocked(true) + ->setDescription( + pht('Whether to require a TLS connection when connecting to MySQL.')), ); } diff --git a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php --- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php @@ -45,6 +45,7 @@ ->setUser($ref->getUser()) ->setHost($ref->getHost()) ->setPort($ref->getPort()) + ->setUseTls($ref->getUseTls()) ->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace()) ->setPassword($ref->getPass()); } diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php --- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php +++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php @@ -22,6 +22,7 @@ private $port; private $user; private $pass; + private $useTls; private $disabled; private $isMaster; private $isIndividual; @@ -80,6 +81,15 @@ return $this->pass; } + public function setUseTls($use_tls) { + $this->useTls = $use_tls; + return $this; + } + + public function getUseTls() { + return $this->useTls; + } + public function setIsMaster($is_master) { $this->isMaster = $is_master; return $this; @@ -325,12 +335,15 @@ $default_pass = phutil_string_cast($default_pass); $default_pass = new PhutilOpaqueEnvelope($default_pass); + $default_use_tls = PhabricatorEnv::getEnvConfig('mysql.use-tls'); + $config = PhabricatorEnv::getEnvConfig('cluster.databases'); return id(new PhabricatorDatabaseRefParser()) ->setDefaultPort($default_port) ->setDefaultUser($default_user) ->setDefaultPass($default_pass) + ->setDefaultUseTls($default_use_tls) ->newRefs($config); } @@ -611,12 +624,14 @@ PhabricatorEnv::getEnvConfig('mysql.pass')); $default_host = PhabricatorEnv::getEnvConfig('mysql.host'); $default_port = PhabricatorEnv::getEnvConfig('mysql.port'); + $default_use_tls = PhabricatorEnv::getEnvConfig('mysql.use-tls'); return id(new self()) ->setUser($default_user) ->setPass($default_pass) ->setHost($default_host) ->setPort($default_port) + ->setUseTls($default_use_tls) ->setIsIndividual(true) ->setIsMaster(true) ->setIsDefaultPartition(true) @@ -707,6 +722,7 @@ 'pass' => $this->getPass(), 'host' => $this->getHost(), 'port' => $this->getPort(), + 'use-tls' => $this->getUseTls(), 'database' => null, 'retries' => $default_retries, 'timeout' => $default_timeout, diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php b/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php --- a/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php +++ b/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php @@ -6,6 +6,7 @@ private $defaultPort = 3306; private $defaultUser; private $defaultPass; + private $defaultUseTls; public function setDefaultPort($default_port) { $this->defaultPort = $default_port; @@ -34,10 +35,20 @@ return $this->defaultPass; } + public function setDefaultUseTls($default_use_tls) { + $this->defaultUseTls = $default_use_tls; + return $this; + } + + public function getDefaultUseTls() { + return $this->defaultUseTls; + } + public function newRefs(array $config) { $default_port = $this->getDefaultPort(); $default_user = $this->getDefaultUser(); $default_pass = $this->getDefaultPass(); + $default_use_tls = $this->getDefaultUseTls(); $refs = array(); @@ -46,6 +57,7 @@ $host = $server['host']; $port = idx($server, 'port', $default_port); $user = idx($server, 'user', $default_user); + $use_tls = idx($server, 'use-tls', $default_use_tls); $disabled = idx($server, 'disabled', false); $pass = idx($server, 'pass'); @@ -65,6 +77,7 @@ ->setPort($port) ->setUser($user) ->setPass($pass) + ->setUseTls($use_tls) ->setDisabled($disabled) ->setIsMaster($is_master) ->setUsePersistentConnections($use_persistent); diff --git a/src/infrastructure/storage/connection/mysql/AphrontMySQLDatabaseConnection.php b/src/infrastructure/storage/connection/mysql/AphrontMySQLDatabaseConnection.php --- a/src/infrastructure/storage/connection/mysql/AphrontMySQLDatabaseConnection.php +++ b/src/infrastructure/storage/connection/mysql/AphrontMySQLDatabaseConnection.php @@ -36,6 +36,15 @@ 'mysql_connect()')); } + $use_tls = $this->getConfiguration('use-tls'); + if ($use_tls) { + // TLS requires AphrontMySQLiDatabaseConnection + throw new Exception( + pht( + 'Using TLS database connections requires the PHP "%s" extension.', + 'mysqli')); + } + $user = $this->getConfiguration('user'); $host = $this->getConfiguration('host'); $port = $this->getConfiguration('port'); diff --git a/src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php b/src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php --- a/src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php +++ b/src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php @@ -71,6 +71,35 @@ $conn->options(MYSQLI_OPT_CONNECT_TIMEOUT, $timeout); } + $flags = 0; + $use_tls = $this->getConfiguration('use-tls'); + if ($use_tls) { + // Below ssl_set() will do nothing if openssl is not enabled, leading to a + // successful insecure connection if the database server does not disable + // plaintext connections. + if (!function_exists('openssl_encrypt')) { + throw new Exception( + pht( + 'Using TLS database connections requires the PHP "%s" extension.', + 'openssl')); + } + + $flags = MYSQLI_CLIENT_SSL; + + // TODO: Support client cert validation? + $client_key = null; + $client_cert = null; + // TODO: Configure allowed ciphers? + $ciphers = null; + + $conn->ssl_set( + $client_key, + $client_cert, + null, // ca_certificate, load from php.ini/openssl.cafile + null, // ca_path, load from php.ini/openssl.capath + $ciphers); + } + if ($this->getPersistent()) { $host = 'p:'.$host; } @@ -82,7 +111,9 @@ $user, $pass, $database, - $port); + $port, + null, // socket + $flags); $call_error = $trap->getErrorsAsString(); $trap->destroy(); diff --git a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php --- a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php +++ b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php @@ -6,6 +6,7 @@ private $host; private $user; private $port; + private $useTls; private $password; private $namespace; private $conns = array(); @@ -76,6 +77,15 @@ return $this->port; } + public function setUseTls($use_tls) { + $this->useTls = $use_tls; + return $this; + } + + public function getUseTls() { + return $this->useTls; + } + public function setRef(PhabricatorDatabaseRef $ref) { $this->ref = $ref; return $this; @@ -131,6 +141,7 @@ 'pass' => $this->password, 'host' => $this->host, 'port' => $this->port, + 'use-tls' => $this->useTls, 'database' => $fragment ? $database : null,