Skip to content

Commit ed35c58

Browse files
omerfeyzioglupetyaslavova
authored andcommitted
Fix RedisCluster ssl_check_hostname not set to connections. For SSL verification with ssl_cert_reqs="none", check_hostname is set to False (redis#3637)
* Fix SSL verification with ssl_cert_reqs=none and ssl_check_hostname=True * Add ssl_check_hostname to REDIS_ALLOWED_KEYS and fix default value in RedisSSLContext
1 parent a2f7e4b commit ed35c58

File tree

8 files changed

+90
-17
lines changed

8 files changed

+90
-17
lines changed

CHANGES

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
* Close Unix sockets if the connection attempt fails. This prevents `ResourceWarning`s. (#3314)
7272
* Close SSL sockets if the connection attempt fails, or if validations fail. (#3317)
7373
* Eliminate mutable default arguments in the `redis.commands.core.Script` class. (#3332)
74+
* Fix SSL verification with `ssl_cert_reqs="none"` and `ssl_check_hostname=True` by automatically setting `check_hostname=False` when `verify_mode=ssl.CERT_NONE` (#3635)
7475
* Allow newer versions of PyJWT as dependency. (#3630)
7576

7677
* 4.1.3 (Feb 8, 2022)

docs/examples/ssl_connection_examples.ipynb

+1-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
" host='localhost',\n",
3838
" port=6666,\n",
3939
" ssl=True,\n",
40-
" ssl_check_hostname=False,\n",
4140
" ssl_cert_reqs=\"none\",\n",
4241
")\n",
4342
"r.ping()"
@@ -69,7 +68,7 @@
6968
"source": [
7069
"import redis\n",
7170
"\n",
72-
"r = redis.from_url(\"rediss://localhost:6666?ssl_cert_reqs=none&ssl_check_hostname=False&decode_responses=True&health_check_interval=2\")\n",
71+
"r = redis.from_url(\"rediss://localhost:6666?ssl_cert_reqs=none&decode_responses=True&health_check_interval=2\")\n",
7372
"r.ping()"
7473
]
7574
},
@@ -103,7 +102,6 @@
103102
" host=\"localhost\",\n",
104103
" port=6666,\n",
105104
" connection_class=redis.SSLConnection,\n",
106-
" ssl_check_hostname=False,\n",
107105
" ssl_cert_reqs=\"none\",\n",
108106
")\n",
109107
"\n",
@@ -143,7 +141,6 @@
143141
" port=6666,\n",
144142
" ssl=True,\n",
145143
" ssl_min_version=ssl.TLSVersion.TLSv1_3,\n",
146-
" ssl_check_hostname=False,\n",
147144
" ssl_cert_reqs=\"none\",\n",
148145
")\n",
149146
"r.ping()"

redis/asyncio/connection.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ def __init__(
868868
cert_reqs: Optional[Union[str, ssl.VerifyMode]] = None,
869869
ca_certs: Optional[str] = None,
870870
ca_data: Optional[str] = None,
871-
check_hostname: bool = False,
871+
check_hostname: bool = True,
872872
min_version: Optional[TLSVersion] = None,
873873
ciphers: Optional[str] = None,
874874
):
@@ -893,7 +893,9 @@ def __init__(
893893
self.cert_reqs = cert_reqs
894894
self.ca_certs = ca_certs
895895
self.ca_data = ca_data
896-
self.check_hostname = check_hostname
896+
self.check_hostname = (
897+
check_hostname if self.cert_reqs != ssl.CERT_NONE else False
898+
)
897899
self.min_version = min_version
898900
self.ciphers = ciphers
899901
self.context: Optional[SSLContext] = None

redis/cluster.py

+1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ def parse_cluster_myshardid(resp, **options):
185185
"ssl_cert_reqs",
186186
"ssl_keyfile",
187187
"ssl_password",
188+
"ssl_check_hostname",
188189
"unix_socket_path",
189190
"username",
190191
"cache",

redis/connection.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,9 @@ def __init__(
10831083
self.ca_certs = ssl_ca_certs
10841084
self.ca_data = ssl_ca_data
10851085
self.ca_path = ssl_ca_path
1086-
self.check_hostname = ssl_check_hostname
1086+
self.check_hostname = (
1087+
ssl_check_hostname if self.cert_reqs != ssl.CERT_NONE else False
1088+
)
10871089
self.certificate_password = ssl_password
10881090
self.ssl_validate_ocsp = ssl_validate_ocsp
10891091
self.ssl_validate_ocsp_stapled = ssl_validate_ocsp_stapled

tests/test_asyncio/test_cluster.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -3139,9 +3139,7 @@ async def test_ssl_with_invalid_cert(
31393139
async def test_ssl_connection(
31403140
self, create_client: Callable[..., Awaitable[RedisCluster]]
31413141
) -> None:
3142-
async with await create_client(
3143-
ssl=True, ssl_check_hostname=False, ssl_cert_reqs="none"
3144-
) as rc:
3142+
async with await create_client(ssl=True, ssl_cert_reqs="none") as rc:
31453143
assert await rc.ping()
31463144

31473145
@pytest.mark.parametrize(
@@ -3157,7 +3155,6 @@ async def test_ssl_connection_tls12_custom_ciphers(
31573155
) -> None:
31583156
async with await create_client(
31593157
ssl=True,
3160-
ssl_check_hostname=False,
31613158
ssl_cert_reqs="none",
31623159
ssl_min_version=ssl.TLSVersion.TLSv1_2,
31633160
ssl_ciphers=ssl_ciphers,
@@ -3169,7 +3166,6 @@ async def test_ssl_connection_tls12_custom_ciphers_invalid(
31693166
) -> None:
31703167
async with await create_client(
31713168
ssl=True,
3172-
ssl_check_hostname=False,
31733169
ssl_cert_reqs="none",
31743170
ssl_min_version=ssl.TLSVersion.TLSv1_2,
31753171
ssl_ciphers="foo:bar",
@@ -3191,7 +3187,6 @@ async def test_ssl_connection_tls13_custom_ciphers(
31913187
# TLSv1.3 does not support changing the ciphers
31923188
async with await create_client(
31933189
ssl=True,
3194-
ssl_check_hostname=False,
31953190
ssl_cert_reqs="none",
31963191
ssl_min_version=ssl.TLSVersion.TLSv1_2,
31973192
ssl_ciphers=ssl_ciphers,

tests/test_asyncio/test_ssl.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from urllib.parse import urlparse
2+
import pytest
3+
import pytest_asyncio
4+
import redis.asyncio as redis
5+
6+
# Skip test or not based on cryptography installation
7+
try:
8+
import cryptography # noqa
9+
10+
skip_if_cryptography = pytest.mark.skipif(False, reason="")
11+
skip_if_nocryptography = pytest.mark.skipif(False, reason="")
12+
except ImportError:
13+
skip_if_cryptography = pytest.mark.skipif(True, reason="cryptography not installed")
14+
skip_if_nocryptography = pytest.mark.skipif(
15+
True, reason="cryptography not installed"
16+
)
17+
18+
19+
@pytest.mark.ssl
20+
class TestSSL:
21+
"""Tests for SSL connections in asyncio."""
22+
23+
@pytest_asyncio.fixture()
24+
async def _get_client(self, request):
25+
ssl_url = request.config.option.redis_ssl_url
26+
p = urlparse(ssl_url)[1].split(":")
27+
client = redis.Redis(host=p[0], port=p[1], ssl=True)
28+
yield client
29+
await client.aclose()
30+
31+
async def test_ssl_with_invalid_cert(self, _get_client):
32+
"""Test SSL connection with invalid certificate."""
33+
pass
34+
35+
async def test_cert_reqs_none_with_check_hostname(self, request):
36+
"""Test that when ssl_cert_reqs=none is used with ssl_check_hostname=True,
37+
the connection is created successfully with check_hostname internally set to False"""
38+
ssl_url = request.config.option.redis_ssl_url
39+
parsed_url = urlparse(ssl_url)
40+
r = redis.Redis(
41+
host=parsed_url.hostname,
42+
port=parsed_url.port,
43+
ssl=True,
44+
ssl_cert_reqs="none",
45+
# Check that ssl_check_hostname is ignored, when ssl_cert_reqs=none
46+
ssl_check_hostname=True,
47+
)
48+
try:
49+
# Connection should be successful
50+
assert await r.ping()
51+
# check_hostname should have been automatically set to False
52+
assert r.connection_pool.connection_class == redis.SSLConnection
53+
conn = r.connection_pool.make_connection()
54+
assert conn.check_hostname is False
55+
finally:
56+
await r.aclose()

tests/test_ssl.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def test_ssl_connection(self, request):
4242
host=p[0],
4343
port=p[1],
4444
ssl=True,
45-
ssl_check_hostname=False,
4645
ssl_cert_reqs="none",
4746
)
4847
assert r.ping()
@@ -105,7 +104,6 @@ def test_ssl_connection_tls12_custom_ciphers(self, request, ssl_ciphers):
105104
host=p[0],
106105
port=p[1],
107106
ssl=True,
108-
ssl_check_hostname=False,
109107
ssl_cert_reqs="none",
110108
ssl_min_version=ssl.TLSVersion.TLSv1_3,
111109
ssl_ciphers=ssl_ciphers,
@@ -120,7 +118,6 @@ def test_ssl_connection_tls12_custom_ciphers_invalid(self, request):
120118
host=p[0],
121119
port=p[1],
122120
ssl=True,
123-
ssl_check_hostname=False,
124121
ssl_cert_reqs="none",
125122
ssl_min_version=ssl.TLSVersion.TLSv1_2,
126123
ssl_ciphers="foo:bar",
@@ -145,7 +142,6 @@ def test_ssl_connection_tls13_custom_ciphers(self, request, ssl_ciphers):
145142
host=p[0],
146143
port=p[1],
147144
ssl=True,
148-
ssl_check_hostname=False,
149145
ssl_cert_reqs="none",
150146
ssl_min_version=ssl.TLSVersion.TLSv1_2,
151147
ssl_ciphers=ssl_ciphers,
@@ -309,3 +305,26 @@ def test_mock_ocsp_staple(self, request):
309305
r.ping()
310306
assert "no ocsp response present" in str(e)
311307
r.close()
308+
309+
def test_cert_reqs_none_with_check_hostname(self, request):
310+
"""Test that when ssl_cert_reqs=none is used with ssl_check_hostname=True,
311+
the connection is created successfully with check_hostname internally set to False"""
312+
ssl_url = request.config.option.redis_ssl_url
313+
parsed_url = urlparse(ssl_url)
314+
r = redis.Redis(
315+
host=parsed_url.hostname,
316+
port=parsed_url.port,
317+
ssl=True,
318+
ssl_cert_reqs="none",
319+
# Check that ssl_check_hostname is ignored, when ssl_cert_reqs=none
320+
ssl_check_hostname=True,
321+
)
322+
try:
323+
# Connection should be successful
324+
assert r.ping()
325+
# check_hostname should have been automatically set to False
326+
assert r.connection_pool.connection_class == redis.SSLConnection
327+
conn = r.connection_pool.make_connection()
328+
assert conn.check_hostname is False
329+
finally:
330+
r.close()

0 commit comments

Comments
 (0)