ai / _utils /_redis_security.py
celik-muhammed's picture
Upload 34 files
89e4531 verified
Raw
History Blame Contribute Delete
3.69 kB
"""
Redis transport-security policy shared by all proxy control planes.
Connection URLs are credentials/configuration, never diagnostics. This module
validates them without returning/logging their authority component and applies
one TLS policy consistently to rate limiting, Global Share, and contribution
lifecycle state.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from urllib.parse import urlsplit
class RedisSecurityError(RuntimeError):
"""Stable, non-sensitive Redis configuration error."""
def __init__(self, code: str) -> None:
super().__init__(code)
self.code = code
@dataclass(frozen=True)
class RedisTransportPolicy:
"""Validated non-secret transport properties."""
tls: bool
scheme: str
database: int
def manifest(self) -> dict[str, Any]:
return {
"transport": "tls_verified" if self.tls else "plaintext",
"tls": self.tls,
"certificate_verification": "required" if self.tls else "not_applicable",
}
def validate_redis_url(url: str, *, require_tls: bool = False) -> RedisTransportPolicy:
"""
Validate a Redis URL without externalizing credentials or host details.
Query parameters are intentionally rejected. Redis-py accepts TLS controls
such as ``ssl_cert_reqs=none`` through URL queries; allowing caller-provided
query policy would make a deployment-wide ``require_tls`` setting
downgradeable from the URL itself. Database selection belongs in the path.
"""
raw = str(url or "").strip()
if not raw:
raise RedisSecurityError("REDIS_URL_REQUIRED")
try:
parsed = urlsplit(raw)
except ValueError as exc:
raise RedisSecurityError("REDIS_URL_INVALID") from exc
scheme = parsed.scheme.lower()
if scheme not in {"redis", "rediss"}:
raise RedisSecurityError("REDIS_SCHEME_UNSUPPORTED")
if not parsed.hostname:
raise RedisSecurityError("REDIS_HOST_REQUIRED")
if parsed.fragment:
raise RedisSecurityError("REDIS_FRAGMENT_FORBIDDEN")
if parsed.query:
raise RedisSecurityError("REDIS_QUERY_FORBIDDEN")
if parsed.path in {"", "/"}:
database = 0
else:
text = parsed.path[1:] if parsed.path.startswith("/") else parsed.path
if not text.isdigit() or not (
0 <= int(text) <= 2_147_483_647 # ruff: ignore[magic-value-comparison]
):
raise RedisSecurityError("REDIS_DATABASE_INVALID")
database = int(text)
tls = scheme == "rediss"
if require_tls and not tls:
raise RedisSecurityError("REDIS_TLS_REQUIRED")
return RedisTransportPolicy(tls=tls, scheme=scheme, database=database)
def redis_connection_kwargs(
url: str,
*,
require_tls: bool,
socket_timeout_seconds: float,
) -> tuple[RedisTransportPolicy, dict[str, Any]]:
"""Return validated non-secret policy plus hardened redis-py kwargs."""
policy = validate_redis_url(url, require_tls=require_tls)
timeout = max(0.25, min(float(socket_timeout_seconds), 10.0))
kwargs: dict[str, Any] = {
"decode_responses": False,
"socket_connect_timeout": timeout,
"socket_timeout": timeout,
"health_check_interval": 30,
}
if policy.tls:
# Never inherit a URL-supplied certificate downgrade. Query strings are
# rejected above and verification is explicitly required here.
kwargs.update(ssl_cert_reqs="required", ssl_check_hostname=True)
return policy, kwargs
__all__ = [
"RedisSecurityError",
"RedisTransportPolicy",
"redis_connection_kwargs",
"validate_redis_url",
]