From c8dc021e138c3ff834a50326a775ed4079f06c0c Mon Sep 17 00:00:00 2001 From: Bakery Admin Date: Sun, 25 Jan 2026 08:43:24 +0100 Subject: [PATCH] Fix redis ssl issues 5 --- services/ai_insights/requirements.txt | 2 +- services/alert_processor/requirements.txt | 2 +- services/distribution/requirements.txt | 2 +- shared/redis_utils/client.py | 165 ++++++---------------- 4 files changed, 43 insertions(+), 128 deletions(-) diff --git a/services/ai_insights/requirements.txt b/services/ai_insights/requirements.txt index 0932e18a..395f6789 100644 --- a/services/ai_insights/requirements.txt +++ b/services/ai_insights/requirements.txt @@ -18,7 +18,7 @@ httpx==0.25.1 aiohttp==3.9.1 # Redis -redis==5.0.1 +redis==6.4.0 hiredis==2.2.3 # Utilities diff --git a/services/alert_processor/requirements.txt b/services/alert_processor/requirements.txt index 4f425c4a..f4477d39 100644 --- a/services/alert_processor/requirements.txt +++ b/services/alert_processor/requirements.txt @@ -15,7 +15,7 @@ psycopg2-binary==2.9.9 aio-pika==9.3.0 # Redis -redis[hiredis]==5.0.1 +redis==6.4.0 # HTTP client httpx==0.25.1 diff --git a/services/distribution/requirements.txt b/services/distribution/requirements.txt index 0a23e9c8..298d32f3 100644 --- a/services/distribution/requirements.txt +++ b/services/distribution/requirements.txt @@ -9,7 +9,7 @@ httpx==0.25.2 pydantic==2.5.0 pydantic-settings==2.1.0 structlog==24.1.0 -redis==5.0.1 +redis==6.4.0 ortools==9.8.3296 # Google OR-Tools for VRP optimization # Message queuing diff --git a/shared/redis_utils/client.py b/shared/redis_utils/client.py index ff2fb456..d003051a 100755 --- a/shared/redis_utils/client.py +++ b/shared/redis_utils/client.py @@ -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()