Fix redis ssl issues 5
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
"""
|
||||
Redis client initialization and connection management
|
||||
Provides standardized Redis connection for all services
|
||||
|
||||
Compatible with redis-py 6.4.0+
|
||||
SSL/TLS support for self-signed certificates in Kubernetes environments
|
||||
"""
|
||||
|
||||
import ssl
|
||||
import redis.asyncio as redis
|
||||
from typing import Optional, Dict, Any
|
||||
import structlog
|
||||
@@ -19,6 +21,8 @@ def get_ssl_kwargs_for_url(redis_url: str) -> Dict[str, Any]:
|
||||
Handles self-signed certificates by disabling certificate verification
|
||||
when using rediss:// (TLS-enabled) URLs.
|
||||
|
||||
For redis-py 6.4.0+, ssl_cert_reqs accepts string values: "none", "optional", "required"
|
||||
|
||||
Args:
|
||||
redis_url: Redis connection URL (redis:// or rediss://)
|
||||
|
||||
@@ -27,10 +31,8 @@ def get_ssl_kwargs_for_url(redis_url: str) -> Dict[str, Any]:
|
||||
"""
|
||||
if redis_url and redis_url.startswith("rediss://"):
|
||||
return {
|
||||
"ssl_cert_reqs": ssl.CERT_NONE, # Disable certificate verification
|
||||
"ssl_ca_certs": None, # Don't require CA certificates
|
||||
"ssl_certfile": None, # Don't require client cert
|
||||
"ssl_keyfile": None, # Don't require client key
|
||||
"ssl_cert_reqs": "none", # Don't verify server certificate (self-signed)
|
||||
"ssl_check_hostname": False, # Don't verify hostname for internal services
|
||||
}
|
||||
return {}
|
||||
|
||||
@@ -133,130 +135,43 @@ class RedisConnectionManager:
|
||||
try:
|
||||
self._redis_url = redis_url
|
||||
|
||||
# Create connection pool with SSL handling for self-signed certificates
|
||||
# For Redis 6.4.0+, we need to handle SSL parameters correctly
|
||||
# Common connection kwargs for both TLS and non-TLS
|
||||
connection_kwargs = {
|
||||
'db': db,
|
||||
'max_connections': max_connections,
|
||||
'decode_responses': decode_responses,
|
||||
'retry_on_timeout': retry_on_timeout,
|
||||
'socket_keepalive': socket_keepalive,
|
||||
'health_check_interval': health_check_interval
|
||||
}
|
||||
|
||||
# For redis-py 6.4.0+, SSL parameters must be passed to ConnectionPool.from_url()
|
||||
# The library handles SSL internally when using rediss:// URLs
|
||||
if redis_url.startswith("rediss://"):
|
||||
# Extract connection parameters from URL
|
||||
from urllib.parse import urlparse
|
||||
|
||||
parsed_url = urlparse(redis_url)
|
||||
|
||||
# Build connection parameters for ConnectionPool
|
||||
connection_params = {
|
||||
'db': db,
|
||||
'max_connections': max_connections,
|
||||
'retry_on_timeout': retry_on_timeout,
|
||||
'socket_keepalive': socket_keepalive,
|
||||
'health_check_interval': health_check_interval
|
||||
# SSL configuration for self-signed certificates (internal K8s traffic)
|
||||
# Redis server has --tls-auth-clients no, so client certs are NOT required
|
||||
# We use ssl_cert_reqs="none" to skip verifying the self-signed server cert
|
||||
ssl_kwargs = {
|
||||
'ssl_cert_reqs': "none", # Don't verify server certificate (self-signed)
|
||||
'ssl_check_hostname': False, # Don't verify hostname (internal service names)
|
||||
}
|
||||
|
||||
# Add password if present
|
||||
if parsed_url.password:
|
||||
connection_params['password'] = parsed_url.password
|
||||
|
||||
# Create connection pool (without SSL parameters - they go to the client)
|
||||
self._pool = redis.ConnectionPool(
|
||||
host=parsed_url.hostname,
|
||||
port=parsed_url.port or 6379,
|
||||
**connection_params
|
||||
)
|
||||
|
||||
# Get SSL configuration for self-signed certificates
|
||||
ssl_kwargs = get_ssl_kwargs_for_url(redis_url)
|
||||
|
||||
# Create Redis client with SSL parameters
|
||||
client_params = {
|
||||
'connection_pool': self._pool,
|
||||
'decode_responses': decode_responses
|
||||
}
|
||||
|
||||
if ssl_kwargs:
|
||||
client_params['ssl'] = True
|
||||
client_params['ssl_cert_reqs'] = ssl_kwargs.get('ssl_cert_reqs', ssl.CERT_NONE)
|
||||
|
||||
# For Kubernetes environments, try to use mounted TLS certificates
|
||||
# These are typically mounted at /tls/redis-cert.pem, /tls/redis-key.pem, /tls/ca-cert.pem
|
||||
import os
|
||||
ca_certs_path = os.getenv('REDIS_CA_CERTS_PATH', '/tls/ca-cert.pem')
|
||||
certfile_path = os.getenv('REDIS_CERTFILE_PATH', '/tls/redis-cert.pem')
|
||||
keyfile_path = os.getenv('REDIS_KEYFILE_PATH', '/tls/redis-key.pem')
|
||||
|
||||
# Use environment variables or mounted files if they exist
|
||||
if os.path.exists(ca_certs_path):
|
||||
client_params['ssl_ca_certs'] = ca_certs_path
|
||||
elif ssl_kwargs.get('ssl_ca_certs'):
|
||||
client_params['ssl_ca_certs'] = ssl_kwargs.get('ssl_ca_certs')
|
||||
|
||||
if os.path.exists(certfile_path):
|
||||
client_params['ssl_certfile'] = certfile_path
|
||||
elif ssl_kwargs.get('ssl_certfile'):
|
||||
client_params['ssl_certfile'] = ssl_kwargs.get('ssl_certfile')
|
||||
|
||||
if os.path.exists(keyfile_path):
|
||||
client_params['ssl_keyfile'] = keyfile_path
|
||||
elif ssl_kwargs.get('ssl_keyfile'):
|
||||
client_params['ssl_keyfile'] = ssl_kwargs.get('ssl_keyfile')
|
||||
|
||||
# Add additional SSL context parameters for better compatibility
|
||||
# These help with SSL handshake issues and protocol compatibility
|
||||
client_params['ssl_check_hostname'] = False # Disable hostname verification for self-signed certs
|
||||
|
||||
# Add SSL context with specific protocol versions for better compatibility
|
||||
# This helps with "wrong version number" and "unexpected eof" SSL errors
|
||||
import ssl as ssl_module
|
||||
ssl_context = ssl_module.create_default_context(
|
||||
purpose=ssl_module.Purpose.SERVER_AUTH,
|
||||
cafile=client_params.get('ssl_ca_certs')
|
||||
)
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = client_params.get('ssl_cert_reqs', ssl_module.CERT_NONE)
|
||||
|
||||
# Set minimum TLS version for better security and compatibility
|
||||
# TLS 1.2 is widely supported and secure enough for internal cluster communication
|
||||
ssl_context.minimum_version = ssl_module.TLSVersion.TLSv1_2
|
||||
|
||||
# If client certificates are provided, load them
|
||||
if client_params.get('ssl_certfile') and client_params.get('ssl_keyfile'):
|
||||
ssl_context.load_cert_chain(
|
||||
certfile=client_params.get('ssl_certfile'),
|
||||
keyfile=client_params.get('ssl_keyfile')
|
||||
)
|
||||
|
||||
client_params['ssl_context'] = ssl_context
|
||||
|
||||
# Debug: Log the SSL configuration being used
|
||||
self.logger.debug(
|
||||
"redis_ssl_config",
|
||||
ssl_enabled=True,
|
||||
ssl_cert_reqs=client_params.get('ssl_cert_reqs'),
|
||||
ssl_ca_certs=client_params.get('ssl_ca_certs'),
|
||||
ssl_certfile=client_params.get('ssl_certfile'),
|
||||
ssl_keyfile=client_params.get('ssl_keyfile'),
|
||||
ssl_check_hostname=False,
|
||||
ssl_minimum_version="TLSv1_2"
|
||||
)
|
||||
|
||||
self._client = redis.Redis(**client_params)
|
||||
else:
|
||||
# For non-TLS connections, use the original approach
|
||||
connection_kwargs = {
|
||||
'db': db,
|
||||
'max_connections': max_connections,
|
||||
'decode_responses': decode_responses,
|
||||
'retry_on_timeout': retry_on_timeout,
|
||||
'socket_keepalive': socket_keepalive,
|
||||
'health_check_interval': health_check_interval
|
||||
}
|
||||
|
||||
# Add SSL kwargs for self-signed certificates (using shared helper)
|
||||
connection_kwargs.update(get_ssl_kwargs_for_url(redis_url))
|
||||
|
||||
self._pool = redis.ConnectionPool.from_url(
|
||||
redis_url,
|
||||
**connection_kwargs
|
||||
|
||||
connection_kwargs.update(ssl_kwargs)
|
||||
|
||||
self.logger.debug(
|
||||
"redis_ssl_config",
|
||||
ssl_enabled=True,
|
||||
ssl_cert_reqs=ssl_kwargs.get('ssl_cert_reqs'),
|
||||
ssl_check_hostname=ssl_kwargs.get('ssl_check_hostname')
|
||||
)
|
||||
|
||||
self._client = redis.Redis(connection_pool=self._pool)
|
||||
# Use from_url for both TLS and non-TLS - it handles SSL automatically for rediss://
|
||||
self._pool = redis.ConnectionPool.from_url(
|
||||
redis_url,
|
||||
**connection_kwargs
|
||||
)
|
||||
|
||||
self._client = redis.Redis(connection_pool=self._pool)
|
||||
|
||||
# Test connection
|
||||
await self._client.ping()
|
||||
|
||||
Reference in New Issue
Block a user