diff --git a/docs/examples/ssl_connection_examples.ipynb b/docs/examples/ssl_connection_examples.ipynb index c94c4e0191..a09b87ec1f 100644 --- a/docs/examples/ssl_connection_examples.ipynb +++ b/docs/examples/ssl_connection_examples.ipynb @@ -34,9 +34,10 @@ "import redis\n", "\n", "r = redis.Redis(\n", - " host='localhost', \n", - " port=6666, \n", - " ssl=True, \n", + " host='localhost',\n", + " port=6666,\n", + " ssl=True,\n", + " ssl_check_hostname=False,\n", " ssl_cert_reqs=\"none\",\n", ")\n", "r.ping()" @@ -68,7 +69,7 @@ "source": [ "import redis\n", "\n", - "r = redis.from_url(\"rediss://localhost:6666?ssl_cert_reqs=none&decode_responses=True&health_check_interval=2\")\n", + "r = redis.from_url(\"rediss://localhost:6666?ssl_cert_reqs=none&ssl_check_hostname=False&decode_responses=True&health_check_interval=2\")\n", "r.ping()" ] }, @@ -99,13 +100,14 @@ "import redis\n", "\n", "redis_pool = redis.ConnectionPool(\n", - " host=\"localhost\", \n", - " port=6666, \n", - " connection_class=redis.SSLConnection, \n", + " host=\"localhost\",\n", + " port=6666,\n", + " connection_class=redis.SSLConnection,\n", + " ssl_check_hostname=False,\n", " ssl_cert_reqs=\"none\",\n", ")\n", "\n", - "r = redis.StrictRedis(connection_pool=redis_pool) \n", + "r = redis.StrictRedis(connection_pool=redis_pool)\n", "r.ping()" ] }, @@ -141,6 +143,7 @@ " port=6666,\n", " ssl=True,\n", " ssl_min_version=ssl.TLSVersion.TLSv1_3,\n", + " ssl_check_hostname=False,\n", " ssl_cert_reqs=\"none\",\n", ")\n", "r.ping()" diff --git a/redis/asyncio/client.py b/redis/asyncio/client.py index ac907b0c10..1cb28e725e 100644 --- a/redis/asyncio/client.py +++ b/redis/asyncio/client.py @@ -241,7 +241,7 @@ def __init__( ssl_cert_reqs: Union[str, VerifyMode] = "required", ssl_ca_certs: Optional[str] = None, ssl_ca_data: Optional[str] = None, - ssl_check_hostname: bool = False, + ssl_check_hostname: bool = True, ssl_min_version: Optional[TLSVersion] = None, ssl_ciphers: Optional[str] = None, max_connections: Optional[int] = None, diff --git a/redis/asyncio/cluster.py b/redis/asyncio/cluster.py index cc5e15bb63..b066a100f6 100644 --- a/redis/asyncio/cluster.py +++ b/redis/asyncio/cluster.py @@ -280,7 +280,7 @@ def __init__( ssl_ca_data: Optional[str] = None, ssl_cert_reqs: Union[str, VerifyMode] = "required", ssl_certfile: Optional[str] = None, - ssl_check_hostname: bool = False, + ssl_check_hostname: bool = True, ssl_keyfile: Optional[str] = None, ssl_min_version: Optional[TLSVersion] = None, ssl_ciphers: Optional[str] = None, diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index 70d7d91898..77131ab951 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -794,7 +794,7 @@ def __init__( ssl_cert_reqs: Union[str, ssl.VerifyMode] = "required", ssl_ca_certs: Optional[str] = None, ssl_ca_data: Optional[str] = None, - ssl_check_hostname: bool = False, + ssl_check_hostname: bool = True, ssl_min_version: Optional[TLSVersion] = None, ssl_ciphers: Optional[str] = None, **kwargs, diff --git a/redis/client.py b/redis/client.py index 138f561974..2ef95600c2 100755 --- a/redis/client.py +++ b/redis/client.py @@ -223,7 +223,7 @@ def __init__( ssl_ca_certs: Optional[str] = None, ssl_ca_path: Optional[str] = None, ssl_ca_data: Optional[str] = None, - ssl_check_hostname: bool = False, + ssl_check_hostname: bool = True, ssl_password: Optional[str] = None, ssl_validate_ocsp: bool = False, ssl_validate_ocsp_stapled: bool = False, diff --git a/redis/connection.py b/redis/connection.py index ddc6991c5c..dab45906d2 100644 --- a/redis/connection.py +++ b/redis/connection.py @@ -1028,7 +1028,7 @@ def __init__( ssl_cert_reqs="required", ssl_ca_certs=None, ssl_ca_data=None, - ssl_check_hostname=False, + ssl_check_hostname=True, ssl_ca_path=None, ssl_password=None, ssl_validate_ocsp=False, diff --git a/tests/test_asyncio/test_cluster.py b/tests/test_asyncio/test_cluster.py index 7d411b578b..aee9ebc86e 100644 --- a/tests/test_asyncio/test_cluster.py +++ b/tests/test_asyncio/test_cluster.py @@ -3118,7 +3118,9 @@ async def test_ssl_with_invalid_cert( async def test_ssl_connection( self, create_client: Callable[..., Awaitable[RedisCluster]] ) -> None: - async with await create_client(ssl=True, ssl_cert_reqs="none") as rc: + async with await create_client( + ssl=True, ssl_check_hostname=False, ssl_cert_reqs="none" + ) as rc: assert await rc.ping() @pytest.mark.parametrize( @@ -3134,6 +3136,7 @@ async def test_ssl_connection_tls12_custom_ciphers( ) -> None: async with await create_client( ssl=True, + ssl_check_hostname=False, ssl_cert_reqs="none", ssl_min_version=ssl.TLSVersion.TLSv1_2, ssl_ciphers=ssl_ciphers, @@ -3145,6 +3148,7 @@ async def test_ssl_connection_tls12_custom_ciphers_invalid( ) -> None: async with await create_client( ssl=True, + ssl_check_hostname=False, ssl_cert_reqs="none", ssl_min_version=ssl.TLSVersion.TLSv1_2, ssl_ciphers="foo:bar", @@ -3166,6 +3170,7 @@ async def test_ssl_connection_tls13_custom_ciphers( # TLSv1.3 does not support changing the ciphers async with await create_client( ssl=True, + ssl_check_hostname=False, ssl_cert_reqs="none", ssl_min_version=ssl.TLSVersion.TLSv1_2, ssl_ciphers=ssl_ciphers, @@ -3177,12 +3182,20 @@ async def test_ssl_connection_tls13_custom_ciphers( async def test_validating_self_signed_certificate( self, create_client: Callable[..., Awaitable[RedisCluster]] ) -> None: + # ssl_check_hostname=False is used to avoid hostname verification + # in the test environment, where the server certificate is self-signed + # and does not match the hostname that is extracted for the cluster. + # Cert hostname is 'localhost' in the cluster initialization when using + # 'localhost' it gets transformed into 127.0.0.1 + # In production code, ssl_check_hostname should be set to True + # to ensure proper hostname verification. async with await create_client( ssl=True, ssl_ca_certs=self.ca_cert, ssl_cert_reqs="required", ssl_certfile=self.client_cert, ssl_keyfile=self.client_key, + ssl_check_hostname=False, ) as rc: assert await rc.ping() @@ -3192,10 +3205,18 @@ async def test_validating_self_signed_string_certificate( with open(self.ca_cert) as f: cert_data = f.read() + # ssl_check_hostname=False is used to avoid hostname verification + # in the test environment, where the server certificate is self-signed + # and does not match the hostname that is extracted for the cluster. + # Cert hostname is 'localhost' in the cluster initialization when using + # 'localhost' it gets transformed into 127.0.0.1 + # In production code, ssl_check_hostname should be set to True + # to ensure proper hostname verification. async with await create_client( ssl=True, ssl_ca_data=cert_data, ssl_cert_reqs="required", + ssl_check_hostname=False, ssl_certfile=self.client_cert, ssl_keyfile=self.client_key, ) as rc: diff --git a/tests/test_asyncio/test_connect.py b/tests/test_asyncio/test_connect.py index 6c4b3c33d7..62e8665d1f 100644 --- a/tests/test_asyncio/test_connect.py +++ b/tests/test_asyncio/test_connect.py @@ -58,6 +58,10 @@ async def test_uds_connect(uds_address): async def test_tcp_ssl_tls12_custom_ciphers(tcp_address, ssl_ciphers): host, port = tcp_address + # in order to have working hostname verification, we need to use "localhost" + # as redis host as the server certificate is self-signed and only valid for "localhost" + host = "localhost" + server_certs = get_tls_certificates(cert_type=CertificateType.server) conn = SSLConnection( @@ -89,6 +93,10 @@ async def test_tcp_ssl_tls12_custom_ciphers(tcp_address, ssl_ciphers): async def test_tcp_ssl_connect(tcp_address, ssl_min_version): host, port = tcp_address + # in order to have working hostname verification, we need to use "localhost" + # as redis host as the server certificate is self-signed and only valid for "localhost" + host = "localhost" + server_certs = get_tls_certificates(cert_type=CertificateType.server) conn = SSLConnection( @@ -100,7 +108,10 @@ async def test_tcp_ssl_connect(tcp_address, ssl_min_version): ssl_min_version=ssl_min_version, ) await _assert_connect( - conn, tcp_address, certfile=server_certs.certfile, keyfile=server_certs.keyfile + conn, + tcp_address, + certfile=server_certs.certfile, + keyfile=server_certs.keyfile, ) await conn.disconnect() diff --git a/tests/test_connect.py b/tests/test_connect.py index f3c02b330f..1e1c23c87e 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -54,10 +54,16 @@ def test_uds_connect(uds_address): ) def test_tcp_ssl_connect(tcp_address, ssl_min_version): host, port = tcp_address + + # in order to have working hostname verification, we need to use "localhost" + # as redis host as the server certificate is self-signed and only valid for "localhost" + host = "localhost" server_certs = get_tls_certificates(cert_type=CertificateType.server) + conn = SSLConnection( host=host, port=port, + ssl_check_hostname=True, client_name=_CLIENT_NAME, ssl_ca_certs=server_certs.ca_certfile, socket_timeout=10, @@ -80,6 +86,10 @@ def test_tcp_ssl_connect(tcp_address, ssl_min_version): def test_tcp_ssl_tls12_custom_ciphers(tcp_address, ssl_ciphers): host, port = tcp_address + # in order to have working hostname verification, we need to use "localhost" + # as redis host as the server certificate is self-signed and only valid for "localhost" + host = "localhost" + server_certs = get_tls_certificates(cert_type=CertificateType.server) conn = SSLConnection( diff --git a/tests/test_ssl.py b/tests/test_ssl.py index 2a945ac287..5aa33353a8 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -37,7 +37,14 @@ def test_ssl_with_invalid_cert(self, request): def test_ssl_connection(self, request): ssl_url = request.config.option.redis_ssl_url p = urlparse(ssl_url)[1].split(":") - r = redis.Redis(host=p[0], port=p[1], ssl=True, ssl_cert_reqs="none") + + r = redis.Redis( + host=p[0], + port=p[1], + ssl=True, + ssl_check_hostname=False, + ssl_cert_reqs="none", + ) assert r.ping() r.close() @@ -98,6 +105,7 @@ def test_ssl_connection_tls12_custom_ciphers(self, request, ssl_ciphers): host=p[0], port=p[1], ssl=True, + ssl_check_hostname=False, ssl_cert_reqs="none", ssl_min_version=ssl.TLSVersion.TLSv1_3, ssl_ciphers=ssl_ciphers, @@ -112,6 +120,7 @@ def test_ssl_connection_tls12_custom_ciphers_invalid(self, request): host=p[0], port=p[1], ssl=True, + ssl_check_hostname=False, ssl_cert_reqs="none", ssl_min_version=ssl.TLSVersion.TLSv1_2, ssl_ciphers="foo:bar", @@ -136,6 +145,7 @@ def test_ssl_connection_tls13_custom_ciphers(self, request, ssl_ciphers): host=p[0], port=p[1], ssl=True, + ssl_check_hostname=False, ssl_cert_reqs="none", ssl_min_version=ssl.TLSVersion.TLSv1_2, ssl_ciphers=ssl_ciphers,