From ed10d85ff36f4fc72eb7c3ba62589105505a999e Mon Sep 17 00:00:00 2001 From: "John Molakvoæ (skjnldsv)" Date: Fri, 9 Jul 2021 10:41:43 +0200 Subject: Support redis user password auth and tls encryption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- lib/private/RedisFactory.php | 106 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 22 deletions(-) (limited to 'lib/private/RedisFactory.php') diff --git a/lib/private/RedisFactory.php b/lib/private/RedisFactory.php index 61168c3dd86..dbac2b58204 100644 --- a/lib/private/RedisFactory.php +++ b/lib/private/RedisFactory.php @@ -27,7 +27,10 @@ namespace OC; class RedisFactory { - /** @var \Redis */ + public const REDIS_MINIMAL_VERSION = '2.2.5'; + public const REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION = '5.3.0'; + + /** @var \Redis|\RedisCluster */ private $instance; /** @var SystemConfig */ @@ -43,25 +46,49 @@ class RedisFactory { } private function create() { - if ($config = $this->config->getValue('redis.cluster', [])) { + $isCluster = !empty($this->config->getValue('redis.cluster', [])); + $config = $this->config->getValue('redis', []); + + // Init cluster config if any + if ($isCluster) { if (!class_exists('RedisCluster')) { throw new \Exception('Redis Cluster support is not available'); } - // cluster config - if (isset($config['timeout'])) { - $timeout = $config['timeout']; - } else { - $timeout = null; - } - if (isset($config['read_timeout'])) { - $readTimeout = $config['read_timeout']; + // Replace config with the cluster config + $config = $this->config->getValue('redis.cluster', []); + } + + if (isset($config['timeout'])) { + $timeout = $config['timeout']; + } else { + $timeout = null; + } + + if (isset($config['read_timeout'])) { + $readTimeout = $config['read_timeout']; + } else { + $readTimeout = null; + } + + $auth = null; + if (isset($config['password']) && $config['password'] !== '') { + if (isset($config['user']) && $config['user'] !== '') { + $auth = [$config['user'], $config['password']]; } else { - $readTimeout = null; + $auth = $config['password']; } - if (isset($config['password']) && $config['password'] !== '') { - $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, false, $config['password']); + } + + // # TLS support + // # https://github.com/phpredis/phpredis/issues/1600 + $connectionParameters = $this->getSslContext($config); + + // cluster config + if ($isCluster) { + if ($connectionParameters !== null) { + $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, false, $auth, $connectionParameters); } else { - $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout); + $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, $auth); } if (isset($config['failover_mode'])) { @@ -69,12 +96,13 @@ class RedisFactory { } } else { $this->instance = new \Redis(); - $config = $this->config->getValue('redis', []); + if (isset($config['host'])) { $host = $config['host']; } else { $host = '127.0.0.1'; } + if (isset($config['port'])) { $port = $config['port']; } elseif ($host[0] !== '/') { @@ -82,15 +110,18 @@ class RedisFactory { } else { $port = null; } - if (isset($config['timeout'])) { - $timeout = $config['timeout']; - } else { - $timeout = 0.0; // unlimited + + if (!empty($connectionParameters)) { + $connectionParameters = [ + 'stream' => $this->getSslContext($config) + ]; } - $this->instance->connect($host, $port, $timeout); - if (isset($config['password']) && $config['password'] !== '') { - $this->instance->auth($config['password']); + $this->instance->connect($host, $port, $timeout, null, 0, $readTimeout, $connectionParameters); + + // Auth if configured + if ($auth !== null) { + $this->instance->auth($auth); } if (isset($config['dbindex'])) { @@ -99,6 +130,26 @@ class RedisFactory { } } + /** + * Get the ssl context config + * + * @param Array $config the current config + * @return Array + * @throws \UnexpectedValueException + */ + private function getSslContext($config) { + if (isset($config['ssl_context'])) { + if (!$this->isConnectionParametersSupported()) { + throw new \UnexpectedValueException(\sprintf( + 'php-redis extension must be version %s or higher to support ssl context', + self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION + )); + } + return $config['ssl_context']; + } + return []; + } + public function getInstance() { if (!$this->isAvailable()) { throw new \Exception('Redis support is not available'); @@ -114,4 +165,15 @@ class RedisFactory { return extension_loaded('redis') && version_compare(phpversion('redis'), '2.2.5', '>='); } + + /** + * Php redis does support configurable extra parameters since version 5.3.0, see: https://github.com/phpredis/phpredis#connect-open. + * We need to check if the current version supports extra connection parameters, otherwise the connect method will throw an exception + * + * @return boolean + */ + private function isConnectionParametersSupported(): bool { + return \extension_loaded('redis') && + \version_compare(\phpversion('redis'), self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION, '>='); + } } -- cgit v1.2.3 From 6e00fe8c26c8d1cd68497911371c3b4b785d903c Mon Sep 17 00:00:00 2001 From: "John Molakvoæ (skjnldsv)" Date: Wed, 21 Jul 2021 12:18:42 +0200 Subject: Properly support RedisCluster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- config/config.sample.php | 4 ++-- lib/private/RedisFactory.php | 38 ++++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 18 deletions(-) (limited to 'lib/private/RedisFactory.php') diff --git a/config/config.sample.php b/config/config.sample.php index 6d6f98db602..ca5dd50b7ee 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1234,7 +1234,7 @@ $CONFIG = [ 'user' => '', // Optional, if not defined no password will be used. 'password' => '', // Optional, if not defined no password will be used. 'dbindex' => 0, // Optional, if undefined SELECT will not run and will use Redis Server's default DB Index. - // If redis is encrypted, provide certificates + // If redis in-transit encryption is enabled, provide certificates // SSL context https://www.php.net/manual/en/context.ssl.php 'ssl_context' => [ 'local_cert' => '/certs/redis.crt', @@ -1278,7 +1278,7 @@ $CONFIG = [ 'failover_mode' => \RedisCluster::FAILOVER_ERROR, 'user' => '', // Optional, if not defined no password will be used. 'password' => '', // Optional, if not defined no password will be used. - // If redis is encrypted, provide certificates + // If redis in-transit encryption is enabled, provide certificates // SSL context https://www.php.net/manual/en/context.ssl.php 'ssl_context' => [ 'local_cert' => '/certs/redis.crt', diff --git a/lib/private/RedisFactory.php b/lib/private/RedisFactory.php index dbac2b58204..7609a75d52d 100644 --- a/lib/private/RedisFactory.php +++ b/lib/private/RedisFactory.php @@ -46,28 +46,29 @@ class RedisFactory { } private function create() { - $isCluster = !empty($this->config->getValue('redis.cluster', [])); - $config = $this->config->getValue('redis', []); + $isCluster = in_array('redis.cluster', $this->config->getKeys()); + $config = $isCluster + ? $this->config->getValue('redis.cluster', []) + : $this->config->getValue('redis', []); - // Init cluster config if any - if ($isCluster) { - if (!class_exists('RedisCluster')) { - throw new \Exception('Redis Cluster support is not available'); - } - // Replace config with the cluster config - $config = $this->config->getValue('redis.cluster', []); + if (empty($config)) { + throw new \Exception('Redis config is empty'); + } + + if ($isCluster && !class_exists('RedisCluster')) { + throw new \Exception('Redis Cluster support is not available'); } if (isset($config['timeout'])) { $timeout = $config['timeout']; } else { - $timeout = null; + $timeout = 0.0; } if (isset($config['read_timeout'])) { $readTimeout = $config['read_timeout']; } else { - $readTimeout = null; + $readTimeout = 0.0; } $auth = null; @@ -85,10 +86,11 @@ class RedisFactory { // cluster config if ($isCluster) { + // Support for older phpredis versions not supporting connectionParameters if ($connectionParameters !== null) { $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, false, $auth, $connectionParameters); } else { - $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, $auth); + $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, false, $auth); } if (isset($config['failover_mode'])) { @@ -111,13 +113,17 @@ class RedisFactory { $port = null; } - if (!empty($connectionParameters)) { + // Support for older phpredis versions not supporting connectionParameters + if ($connectionParameters !== null) { + // Non-clustered redis requires connection parameters to be wrapped inside `stream` $connectionParameters = [ 'stream' => $this->getSslContext($config) ]; + $this->instance->connect($host, $port, $timeout, null, 0, $readTimeout, $connectionParameters); + } else { + $this->instance->connect($host, $port, $timeout, null, 0, $readTimeout); } - $this->instance->connect($host, $port, $timeout, null, 0, $readTimeout, $connectionParameters); // Auth if configured if ($auth !== null) { @@ -134,7 +140,7 @@ class RedisFactory { * Get the ssl context config * * @param Array $config the current config - * @return Array + * @return Array|null * @throws \UnexpectedValueException */ private function getSslContext($config) { @@ -147,7 +153,7 @@ class RedisFactory { } return $config['ssl_context']; } - return []; + return null; } public function getInstance() { -- cgit v1.2.3