Skip to content

Commit 1f04ddc

Browse files
omerfeyziogluManelCoutinhoSensei
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 b7e16e0 commit 1f04ddc

File tree

8 files changed

+90
-17
lines changed

8 files changed

+90
-17
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 4 deletions
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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ def __init__(
898898
cert_reqs: Optional[Union[str, ssl.VerifyMode]] = None,
899899
ca_certs: Optional[str] = None,
900900
ca_data: Optional[str] = None,
901-
check_hostname: bool = False,
901+
check_hostname: bool = True,
902902
min_version: Optional[TLSVersion] = None,
903903
ciphers: Optional[str] = None,
904904
):
@@ -923,7 +923,9 @@ def __init__(
923923
self.cert_reqs = cert_reqs
924924
self.ca_certs = ca_certs
925925
self.ca_data = ca_data
926-
self.check_hostname = check_hostname
926+
self.check_hostname = (
927+
check_hostname if self.cert_reqs != ssl.CERT_NONE else False
928+
)
927929
self.min_version = min_version
928930
self.ciphers = ciphers
929931
self.context: Optional[SSLContext] = None

redis/cluster.py

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,9 @@ def __init__(
11051105
self.ca_certs = ssl_ca_certs
11061106
self.ca_data = ssl_ca_data
11071107
self.ca_path = ssl_ca_path
1108-
self.check_hostname = ssl_check_hostname
1108+
self.check_hostname = (
1109+
ssl_check_hostname if self.cert_reqs != ssl.CERT_NONE else False
1110+
)
11091111
self.certificate_password = ssl_password
11101112
self.ssl_validate_ocsp = ssl_validate_ocsp
11111113
self.ssl_validate_ocsp_stapled = ssl_validate_ocsp_stapled

tests/test_asyncio/test_cluster.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3124,9 +3124,7 @@ async def test_ssl_with_invalid_cert(
31243124
async def test_ssl_connection(
31253125
self, create_client: Callable[..., Awaitable[RedisCluster]]
31263126
) -> None:
3127-
async with await create_client(
3128-
ssl=True, ssl_check_hostname=False, ssl_cert_reqs="none"
3129-
) as rc:
3127+
async with await create_client(ssl=True, ssl_cert_reqs="none") as rc:
31303128
assert await rc.ping()
31313129

31323130
@pytest.mark.parametrize(
@@ -3142,7 +3140,6 @@ async def test_ssl_connection_tls12_custom_ciphers(
31423140
) -> None:
31433141
async with await create_client(
31443142
ssl=True,
3145-
ssl_check_hostname=False,
31463143
ssl_cert_reqs="none",
31473144
ssl_min_version=ssl.TLSVersion.TLSv1_2,
31483145
ssl_ciphers=ssl_ciphers,
@@ -3154,7 +3151,6 @@ async def test_ssl_connection_tls12_custom_ciphers_invalid(
31543151
) -> None:
31553152
async with await create_client(
31563153
ssl=True,
3157-
ssl_check_hostname=False,
31583154
ssl_cert_reqs="none",
31593155
ssl_min_version=ssl.TLSVersion.TLSv1_2,
31603156
ssl_ciphers="foo:bar",
@@ -3176,7 +3172,6 @@ async def test_ssl_connection_tls13_custom_ciphers(
31763172
# TLSv1.3 does not support changing the ciphers
31773173
async with await create_client(
31783174
ssl=True,
3179-
ssl_check_hostname=False,
31803175
ssl_cert_reqs="none",
31813176
ssl_min_version=ssl.TLSVersion.TLSv1_2,
31823177
ssl_ciphers=ssl_ciphers,

tests/test_asyncio/test_ssl.py

Lines changed: 56 additions & 0 deletions
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

Lines changed: 23 additions & 4 deletions
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()
@@ -112,7 +111,6 @@ def test_ssl_connection_tls12_custom_ciphers(self, request, ssl_ciphers):
112111
host=p[0],
113112
port=p[1],
114113
ssl=True,
115-
ssl_check_hostname=False,
116114
ssl_cert_reqs="none",
117115
ssl_min_version=ssl.TLSVersion.TLSv1_3,
118116
ssl_ciphers=ssl_ciphers,
@@ -127,7 +125,6 @@ def test_ssl_connection_tls12_custom_ciphers_invalid(self, request):
127125
host=p[0],
128126
port=p[1],
129127
ssl=True,
130-
ssl_check_hostname=False,
131128
ssl_cert_reqs="none",
132129
ssl_min_version=ssl.TLSVersion.TLSv1_2,
133130
ssl_ciphers="foo:bar",
@@ -152,7 +149,6 @@ def test_ssl_connection_tls13_custom_ciphers(self, request, ssl_ciphers):
152149
host=p[0],
153150
port=p[1],
154151
ssl=True,
155-
ssl_check_hostname=False,
156152
ssl_cert_reqs="none",
157153
ssl_min_version=ssl.TLSVersion.TLSv1_2,
158154
ssl_ciphers=ssl_ciphers,
@@ -316,3 +312,26 @@ def test_mock_ocsp_staple(self, request):
316312
r.ping()
317313
assert "no ocsp response present" in str(e)
318314
r.close()
315+
316+
def test_cert_reqs_none_with_check_hostname(self, request):
317+
"""Test that when ssl_cert_reqs=none is used with ssl_check_hostname=True,
318+
the connection is created successfully with check_hostname internally set to False"""
319+
ssl_url = request.config.option.redis_ssl_url
320+
parsed_url = urlparse(ssl_url)
321+
r = redis.Redis(
322+
host=parsed_url.hostname,
323+
port=parsed_url.port,
324+
ssl=True,
325+
ssl_cert_reqs="none",
326+
# Check that ssl_check_hostname is ignored, when ssl_cert_reqs=none
327+
ssl_check_hostname=True,
328+
)
329+
try:
330+
# Connection should be successful
331+
assert r.ping()
332+
# check_hostname should have been automatically set to False
333+
assert r.connection_pool.connection_class == redis.SSLConnection
334+
conn = r.connection_pool.make_connection()
335+
assert conn.check_hostname is False
336+
finally:
337+
r.close()

0 commit comments

Comments
 (0)