Fix redis ssl issues 5
This commit is contained in:
@@ -18,7 +18,7 @@ httpx==0.25.1
|
|||||||
aiohttp==3.9.1
|
aiohttp==3.9.1
|
||||||
|
|
||||||
# Redis
|
# Redis
|
||||||
redis==5.0.1
|
redis==6.4.0
|
||||||
hiredis==2.2.3
|
hiredis==2.2.3
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ psycopg2-binary==2.9.9
|
|||||||
aio-pika==9.3.0
|
aio-pika==9.3.0
|
||||||
|
|
||||||
# Redis
|
# Redis
|
||||||
redis[hiredis]==5.0.1
|
redis==6.4.0
|
||||||
|
|
||||||
# HTTP client
|
# HTTP client
|
||||||
httpx==0.25.1
|
httpx==0.25.1
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ httpx==0.25.2
|
|||||||
pydantic==2.5.0
|
pydantic==2.5.0
|
||||||
pydantic-settings==2.1.0
|
pydantic-settings==2.1.0
|
||||||
structlog==24.1.0
|
structlog==24.1.0
|
||||||
redis==5.0.1
|
redis==6.4.0
|
||||||
ortools==9.8.3296 # Google OR-Tools for VRP optimization
|
ortools==9.8.3296 # Google OR-Tools for VRP optimization
|
||||||
|
|
||||||
# Message queuing
|
# Message queuing
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
Redis client initialization and connection management
|
Redis client initialization and connection management
|
||||||
Provides standardized Redis connection for all services
|
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
|
import redis.asyncio as redis
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
import structlog
|
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
|
Handles self-signed certificates by disabling certificate verification
|
||||||
when using rediss:// (TLS-enabled) URLs.
|
when using rediss:// (TLS-enabled) URLs.
|
||||||
|
|
||||||
|
For redis-py 6.4.0+, ssl_cert_reqs accepts string values: "none", "optional", "required"
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
redis_url: Redis connection URL (redis:// or rediss://)
|
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://"):
|
if redis_url and redis_url.startswith("rediss://"):
|
||||||
return {
|
return {
|
||||||
"ssl_cert_reqs": ssl.CERT_NONE, # Disable certificate verification
|
"ssl_cert_reqs": "none", # Don't verify server certificate (self-signed)
|
||||||
"ssl_ca_certs": None, # Don't require CA certificates
|
"ssl_check_hostname": False, # Don't verify hostname for internal services
|
||||||
"ssl_certfile": None, # Don't require client cert
|
|
||||||
"ssl_keyfile": None, # Don't require client key
|
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -133,130 +135,43 @@ class RedisConnectionManager:
|
|||||||
try:
|
try:
|
||||||
self._redis_url = redis_url
|
self._redis_url = redis_url
|
||||||
|
|
||||||
# Create connection pool with SSL handling for self-signed certificates
|
# Common connection kwargs for both TLS and non-TLS
|
||||||
# For Redis 6.4.0+, we need to handle SSL parameters correctly
|
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://"):
|
if redis_url.startswith("rediss://"):
|
||||||
# Extract connection parameters from URL
|
# SSL configuration for self-signed certificates (internal K8s traffic)
|
||||||
from urllib.parse import urlparse
|
# 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
|
||||||
parsed_url = urlparse(redis_url)
|
ssl_kwargs = {
|
||||||
|
'ssl_cert_reqs': "none", # Don't verify server certificate (self-signed)
|
||||||
# Build connection parameters for ConnectionPool
|
'ssl_check_hostname': False, # Don't verify hostname (internal service names)
|
||||||
connection_params = {
|
|
||||||
'db': db,
|
|
||||||
'max_connections': max_connections,
|
|
||||||
'retry_on_timeout': retry_on_timeout,
|
|
||||||
'socket_keepalive': socket_keepalive,
|
|
||||||
'health_check_interval': health_check_interval
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add password if present
|
connection_kwargs.update(ssl_kwargs)
|
||||||
if parsed_url.password:
|
|
||||||
connection_params['password'] = parsed_url.password
|
|
||||||
|
|
||||||
# Create connection pool (without SSL parameters - they go to the client)
|
self.logger.debug(
|
||||||
self._pool = redis.ConnectionPool(
|
"redis_ssl_config",
|
||||||
host=parsed_url.hostname,
|
ssl_enabled=True,
|
||||||
port=parsed_url.port or 6379,
|
ssl_cert_reqs=ssl_kwargs.get('ssl_cert_reqs'),
|
||||||
**connection_params
|
ssl_check_hostname=ssl_kwargs.get('ssl_check_hostname')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get SSL configuration for self-signed certificates
|
# Use from_url for both TLS and non-TLS - it handles SSL automatically for rediss://
|
||||||
ssl_kwargs = get_ssl_kwargs_for_url(redis_url)
|
self._pool = redis.ConnectionPool.from_url(
|
||||||
|
redis_url,
|
||||||
|
**connection_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
# Create Redis client with SSL parameters
|
self._client = redis.Redis(connection_pool=self._pool)
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
self._client = redis.Redis(connection_pool=self._pool)
|
|
||||||
|
|
||||||
# Test connection
|
# Test connection
|
||||||
await self._client.ping()
|
await self._client.ping()
|
||||||
|
|||||||
Reference in New Issue
Block a user