Skip to content

Commit 0894218

Browse files
majiayu000claudepetyaslavova
authored
Add ssl_password support to async Redis client (#3878)
* Add ssl_password support to async Redis client (#3347) Add ssl_password parameter to the async Redis client and SSLConnection to enable using encrypted private keys, matching the sync client's functionality. Changes: - Add ssl_password parameter to Redis.__init__ - Add ssl_password parameter to SSLConnection.__init__ - Add password parameter to RedisSSLContext - Pass password to ssl.SSLContext.load_cert_chain() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add test for ssl_password parameter Add test_ssl_password_parameter test to verify ssl_password is properly passed through to SSLConnection for encrypted private key support. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: petyaslavova <petya.slavova@redis.com>
1 parent 201c3c9 commit 0894218

File tree

3 files changed

+39
-2
lines changed

3 files changed

+39
-2
lines changed

redis/asyncio/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ def __init__(
247247
ssl_check_hostname: bool = True,
248248
ssl_min_version: Optional[TLSVersion] = None,
249249
ssl_ciphers: Optional[str] = None,
250+
ssl_password: Optional[str] = None,
250251
max_connections: Optional[int] = None,
251252
single_connection_client: bool = False,
252253
health_check_interval: int = 0,
@@ -359,6 +360,7 @@ def __init__(
359360
"ssl_check_hostname": ssl_check_hostname,
360361
"ssl_min_version": ssl_min_version,
361362
"ssl_ciphers": ssl_ciphers,
363+
"ssl_password": ssl_password,
362364
}
363365
)
364366
# This arg only used if no pool is passed in

redis/asyncio/connection.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,7 @@ def __init__(
817817
ssl_check_hostname: bool = True,
818818
ssl_min_version: Optional[TLSVersion] = None,
819819
ssl_ciphers: Optional[str] = None,
820+
ssl_password: Optional[str] = None,
820821
**kwargs,
821822
):
822823
if not SSL_AVAILABLE:
@@ -834,6 +835,7 @@ def __init__(
834835
check_hostname=ssl_check_hostname,
835836
min_version=ssl_min_version,
836837
ciphers=ssl_ciphers,
838+
password=ssl_password,
837839
)
838840
super().__init__(**kwargs)
839841

@@ -893,6 +895,7 @@ class RedisSSLContext:
893895
"check_hostname",
894896
"min_version",
895897
"ciphers",
898+
"password",
896899
)
897900

898901
def __init__(
@@ -908,6 +911,7 @@ def __init__(
908911
check_hostname: bool = False,
909912
min_version: Optional[TLSVersion] = None,
910913
ciphers: Optional[str] = None,
914+
password: Optional[str] = None,
911915
):
912916
if not SSL_AVAILABLE:
913917
raise RedisError("Python wasn't built with SSL support")
@@ -938,6 +942,7 @@ def __init__(
938942
)
939943
self.min_version = min_version
940944
self.ciphers = ciphers
945+
self.password = password
941946
self.context: Optional[SSLContext] = None
942947

943948
def get(self) -> SSLContext:
@@ -951,8 +956,12 @@ def get(self) -> SSLContext:
951956
if self.exclude_verify_flags:
952957
for flag in self.exclude_verify_flags:
953958
context.verify_flags &= ~flag
954-
if self.certfile and self.keyfile:
955-
context.load_cert_chain(certfile=self.certfile, keyfile=self.keyfile)
959+
if self.certfile or self.keyfile:
960+
context.load_cert_chain(
961+
certfile=self.certfile,
962+
keyfile=self.keyfile,
963+
password=self.password,
964+
)
956965
if self.ca_certs or self.ca_data or self.ca_path:
957966
context.load_verify_locations(
958967
cafile=self.ca_certs, capath=self.ca_path, cadata=self.ca_data

tests/test_asyncio/test_ssl.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,29 @@ async def test_ssl_ca_path_parameter(self, request):
167167
assert conn.ssl_context.ca_path == test_ca_path
168168
finally:
169169
await r.aclose()
170+
171+
async def test_ssl_password_parameter(self, request):
172+
"""Test that ssl_password parameter is properly passed to SSLConnection"""
173+
ssl_url = request.config.option.redis_ssl_url
174+
parsed_url = urlparse(ssl_url)
175+
176+
# Test with a mock password for encrypted private key
177+
test_password = "test_key_password"
178+
179+
r = redis.Redis(
180+
host=parsed_url.hostname,
181+
port=parsed_url.port,
182+
ssl=True,
183+
ssl_cert_reqs="none",
184+
ssl_password=test_password,
185+
)
186+
187+
try:
188+
# Get the connection to verify ssl_password is passed through
189+
conn = r.connection_pool.make_connection()
190+
assert isinstance(conn, redis.SSLConnection)
191+
192+
# Verify the password is stored in the SSL context
193+
assert conn.ssl_context.password == test_password
194+
finally:
195+
await r.aclose()

0 commit comments

Comments
 (0)