From d8a99ef674cac1fd5054127bc034beaf200382f3 Mon Sep 17 00:00:00 2001
From: Vladyslav Vildanov <117659936+vladvildanov@users.noreply.github.com>
Date: Wed, 4 Feb 2026 11:18:53 +0200
Subject: [PATCH] Added testing with SSL authentication using CN (#1627)
* Added testing with SSL authentication using CN
* Update CHANGELOG.md
* Codestyle changes
* Fixed broken tests
* Fixed hybdrid tests
* Fixed SSL tests
* Fixed array merging
* Updated README.md
* Updated spelling list
* Added support for Redis 8.6
* Codestyle changes and test fixes
* Fixed hybdrid tests
* Updated CHANGELOG.md and composer.json
* Fixed XINFO tests
---
.github/docker-compose.yml | 2 +
.github/wordlist.txt | 4 +
CHANGELOG.md | 1 +
README.md | 34 ++++++++
phpunit.xml.dist | 5 ++
src/Cluster/ClusterStrategy.php | 3 +
tests/PHPUnit/PredisTestCase.php | 12 ++-
tests/Predis/Cluster/PredisStrategyTest.php | 3 +
tests/Predis/Cluster/RedisStrategyTest.php | 3 +
tests/Predis/SSLTest.php | 89 ++++++++++++++++++++-
10 files changed, 150 insertions(+), 6 deletions(-)
diff --git a/.github/docker-compose.yml b/.github/docker-compose.yml
index d4090c77..ce032bf8 100644
--- a/.github/docker-compose.yml
+++ b/.github/docker-compose.yml
@@ -24,6 +24,7 @@ services:
container_name: redis-standalone
environment:
- TLS_ENABLED=yes
+ - TLS_CLIENT_CNS=test_user
- REDIS_CLUSTER=no
- REDIS_PASSWORD=foobar
- PORT=6379
@@ -66,6 +67,7 @@ services:
- NODES=6
- REPLICAS=1
- TLS_ENABLED=yes
+ - TLS_CLIENT_CNS=test_user
- PORT=6372
- TLS_PORT=27379
command: ${REDIS_EXTRA_ARGS:---enable-debug-command yes --enable-module-command yes --tls-auth-clients optional --save "" --requirepass "foobar"}
diff --git a/.github/wordlist.txt b/.github/wordlist.txt
index b199dd7e..a8cc67d4 100644
--- a/.github/wordlist.txt
+++ b/.github/wordlist.txt
@@ -1,11 +1,15 @@
ACLs
+AUTH
Autoloading
+cafile
CAS
+CN
Customizable
ElastiCache
FPM
GC
IANA
+mTLS
Lua
PSR
Packagist
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cff31819..aebe2573 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@
### Maintenance
- Added testing with SSL connection (#1624)
+- Added testing with SSL authentication using CN (#1627)
- Added support for Redis 8.6 (#1631)
## v3.3.0 (2025-11-24)
diff --git a/README.md b/README.md
index b44bfaff..61743b19 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,40 @@ The connection schemes [`redis`](http://www.iana.org/assignments/uri-schemes/pro
also supported, with the difference that URI strings containing these schemes are parsed following
the rules described on their respective IANA provisional registration documents.
+Since Redis 8.6, you can authenticate a client using the Subject CN from its TLS client certificate (mTLS).
+When this is enabled on the server, the client is authenticated during the TLS handshake, so you don’t need
+to send an AUTH command.
+
+To use this, configure:
+
+- a CA certificate used to verify the server certificate (cafile),
+- a client certificate (local_cert) signed by a CA trusted by the Redis server for client authentication,
+- the corresponding private key (local_pk).
+
+Make sure:
+
+- the Redis server certificate is signed by a CA trusted by the client, and
+- the client certificate is signed by a CA trusted by the Redis server (mTLS).
+
+```php
+// Named array of connection parameters:
+$client = new Predis\Client([
+ 'scheme' => 'tls',
+ 'ssl' => [
+ 'cafile' => 'ca.pem', // CA used to verify the server certificate
+ 'local_cert' => 'client.crt', // client certificate (Subject CN maps to ACL user)
+ 'local_pk' => 'client.key', // client private key
+ 'verify_peer' => true,
+ ],
+]);
+
+// ACL user must exist and match the certificate Subject CN (example: CN=CN_NAME).
+// Enable the user and grant permissions as needed:
+$client->acl->setUser('CN_NAME', 'on', '>clientpass', 'allcommands', 'allkeys')
+
+echo $client->acl->whoami() // CN_NAME
+```
+
The actual list of supported connection parameters can vary depending on each connection backend so
it is recommended to refer to their specific documentation or implementation for details.
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 51382a29..e54283b9 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -57,8 +57,13 @@
+
+
+
+
+
[$this, 'getFakeKey'],
+
+ /* control */
+ 'ACL' => [$this, 'getFakeKey'],
];
}
diff --git a/tests/PHPUnit/PredisTestCase.php b/tests/PHPUnit/PredisTestCase.php
index 0a1ad099..6a2e28a7 100644
--- a/tests/PHPUnit/PredisTestCase.php
+++ b/tests/PHPUnit/PredisTestCase.php
@@ -286,27 +286,31 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
);
if ($this->isSSLTest()) {
- $options = array_merge($options, [
+ $options = $options + [
'parameters' => [
'ssl' => [
'cafile' => getenv('CLUSTER_CA_CERT_PATH'),
+ 'local_cert' => getenv('CLUSTER_LOCAL_CERT_PATH'),
+ 'local_pk' => getenv('CLUSTER_LOCAL_PK_PATH'),
'verify_peer' => true,
'verify_peer_name' => false,
'allow_self_signed' => false,
],
],
- ]);
+ ];
}
} else {
if ($this->isSSLTest()) {
- $parameters = array_merge($parameters, [
+ $parameters = $parameters + [
'ssl' => [
'cafile' => getenv('STANDALONE_CA_CERT_PATH'),
+ 'local_cert' => getenv('STANDALONE_LOCAL_CERT_PATH'),
+ 'local_pk' => getenv('STANDALONE_LOCAL_PK_PATH'),
'verify_peer' => true,
'verify_peer_name' => false,
'allow_self_signed' => false,
],
- ]);
+ ];
}
}
diff --git a/tests/Predis/Cluster/PredisStrategyTest.php b/tests/Predis/Cluster/PredisStrategyTest.php
index d1d60a2c..6e6f9d81 100644
--- a/tests/Predis/Cluster/PredisStrategyTest.php
+++ b/tests/Predis/Cluster/PredisStrategyTest.php
@@ -508,6 +508,9 @@ class PredisStrategyTest extends PredisTestCase
/* cluster */
'CLUSTER' => 'keys-fake',
+
+ /* control */
+ 'ACL' => 'keys-fake',
];
if (isset($type)) {
diff --git a/tests/Predis/Cluster/RedisStrategyTest.php b/tests/Predis/Cluster/RedisStrategyTest.php
index 70035b95..be2bee8b 100644
--- a/tests/Predis/Cluster/RedisStrategyTest.php
+++ b/tests/Predis/Cluster/RedisStrategyTest.php
@@ -531,6 +531,9 @@ class RedisStrategyTest extends PredisTestCase
/* cluster */
'CLUSTER' => 'keys-fake',
+
+ /* control */
+ 'ACL' => 'keys-fake',
];
if (isset($type)) {
diff --git a/tests/Predis/SSLTest.php b/tests/Predis/SSLTest.php
index 3533eb79..bfa8f7fa 100644
--- a/tests/Predis/SSLTest.php
+++ b/tests/Predis/SSLTest.php
@@ -26,7 +26,13 @@ class SSLTest extends PredisTestCase
*/
public function testExecuteCommandOverSSLConnection()
{
- $redis = $this->createClient();
+ $redis = $this->createClient([
+ 'ssl' => [
+ 'cafile' => getenv('STANDALONE_CA_CERT_PATH'),
+ 'verify_peer' => true,
+ 'verify_peer_name' => false,
+ ],
+ ]);
$this->assertEquals('PONG', $redis->ping());
}
@@ -76,7 +82,16 @@ class SSLTest extends PredisTestCase
*/
public function testClusterExecuteCommandOverSSLConnection()
{
- $redis = $this->createClient();
+ $redis = $this->createClient(null, [
+ 'cluster' => 'redis',
+ 'parameters' => [
+ 'ssl' => [
+ 'cafile' => getenv('CLUSTER_CA_CERT_PATH'),
+ 'verify_peer' => true,
+ 'verify_peer_name' => false,
+ ],
+ ],
+ ]);
$redis->set('foo', 'bar');
$this->assertEquals('bar', $redis->get('foo'));
}
@@ -123,4 +138,74 @@ class SSLTest extends PredisTestCase
$redis->set('foo', 'bar');
}
+
+ /**
+ * @group connected
+ * @group ssl
+ * @group relay-incompatible
+ * @requiresRedisVersion >= 8.5.0
+ * @return void
+ */
+ public function testAuthWithSSLCertificateWithCNSpecified()
+ {
+ $redis = $this->createClient();
+
+ $this->assertEquals(
+ 'OK',
+ $redis->acl->setUser('test_user', 'on', '>clientpass', 'allcommands', 'allkeys')
+ );
+
+ $redis->disconnect();
+
+ // Remove AUTH
+ $redis = $this->createClient(['password' => null]);
+
+ $this->assertEquals(getenv('CN_USER_NAME'), $redis->acl->whoami());
+ $this->assertEquals(1, $redis->acl->delUser(getenv('CN_USER_NAME')));
+ }
+
+ /**
+ * @group connected
+ * @group ssl
+ * @group cluster
+ * @group relay-incompatible
+ * @requiresRedisVersion >= 8.5.0
+ * @return void
+ */
+ public function testClusterAuthWithSSLCertificateWithCNSpecified()
+ {
+ $redis = $this->createClient();
+
+ $this->assertEquals(
+ 'OK',
+ $redis->acl->setUser('test_user', 'on', '>clientpass', 'allcommands', 'allkeys')
+ );
+
+ $redis->disconnect();
+
+ // Remove AUTH
+ $defaultParameters = $this->getDefaultParametersArray();
+ $trimmedParameters = array_map(function (string $parameter) {
+ return explode('?', $parameter)[0];
+ }, $defaultParameters);
+
+ $redis = new Client(
+ $trimmedParameters,
+ [
+ 'cluster' => 'redis',
+ 'parameters' => [
+ 'ssl' => [
+ 'cafile' => getenv('CLUSTER_CA_CERT_PATH'),
+ 'local_cert' => getenv('CLUSTER_LOCAL_CERT_PATH'),
+ 'local_pk' => getenv('CLUSTER_LOCAL_PK_PATH'),
+ 'verify_peer' => true,
+ 'verify_peer_name' => false,
+ ],
+ ],
+ ]
+ );
+
+ $this->assertEquals(getenv('CN_USER_NAME'), $redis->acl->whoami());
+ $this->assertEquals(1, $redis->acl->delUser(getenv('CN_USER_NAME')));
+ }
}