diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 69e3559da..77aca0129 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -18,6 +18,7 @@ services: - "5433:5432" networks: - sapl-net + saplsolr: image: solr:8.11 restart: always @@ -32,6 +33,34 @@ services: - "8983:8983" networks: - sapl-net + + saplredis: + image: redis:7-alpine + restart: always + container_name: redis + labels: + NAME: "redis" + command: > + redis-server + --save "" + --appendonly no + --maxmemory 512mb + --maxmemory-policy allkeys-lru + --maxmemory-samples 10 + --maxclients 1000 + --timeout 300 + --tcp-keepalive 60 + --hz 20 + --lazyfree-lazy-eviction yes + --lazyfree-lazy-expire yes + --lazyfree-lazy-server-del yes + --databases 4 + --protected-mode no + ports: + - "6379:6379" + networks: + - sapl-net + sapl: image: sapl:local # build: @@ -57,20 +86,25 @@ services: IS_ZK_EMBEDDED: 'True' ENABLE_SAPN: 'False' TZ: America/Sao_Paulo + REDIS_URL: redis://saplredis:6379 + CACHE_BACKEND: redis volumes: - sapl_data:/var/interlegis/sapl/data - sapl_media:/var/interlegis/sapl/media depends_on: - sapldb - saplsolr + - saplredis ports: - "80:80" networks: - sapl-net + networks: sapl-net: name: sapl-net driver: bridge + volumes: sapldb_data: sapl_data: diff --git a/sapl/middleware/ratelimit.py b/sapl/middleware/ratelimit.py index 103983d33..0ab70b7d5 100644 --- a/sapl/middleware/ratelimit.py +++ b/sapl/middleware/ratelimit.py @@ -16,11 +16,12 @@ Decision flow (per request): All decisions are no-ops when RATELIMIT_DRY_RUN=True (logged only). Degrades gracefully to non-atomic counting when Redis is unavailable. -Tenant namespace (_NAMESPACE) is resolved once at module load from: - 1. POD_NAMESPACE env var (K8s Downward API — preferred) - 2. K8s service-account namespace file (always present in-cluster) - 3. 'global' (local development fallback) -Since each pod serves exactly one tenant, this is a startup constant — +_NAMESPACE is settings.POD_NAMESPACE, resolved once at startup: + - K8s: start.sh reads the k8s namespace from the Downward API env var + or the service-account namespace file, writes it to .env as POD_NAMESPACE. + - Bare-metal / VM / docker-compose: defaults to the machine hostname + (socket.gethostbyname_ex result computed in settings.py). +Since a deployment serves exactly one tenant, this is a startup constant — no per-request lookup is needed or correct. """ @@ -46,11 +47,11 @@ _NAMESPACE = settings.POD_NAMESPACE # Redis key templates — module-level constants, never inline strings # --------------------------------------------------------------------------- -RL_IP_REQUESTS = 'rl:ip:{ip}:reqs' -RL_IP_BLOCKED = 'rl:ip:{ip}:blocked' +RL_IP_REQUESTS = 'rl:ip:{ip}:reqs' +RL_IP_BLOCKED = 'rl:ip:{ip}:blocked' RL_USER_REQUESTS = 'rl:{ns}:user:{uid}:reqs' -RL_USER_BLOCKED = 'rl:{ns}:user:{uid}:blocked' -RL_NS_WINDOW = 'rl:{ns}:ip:{ip}:w:{bucket}' +RL_USER_BLOCKED = 'rl:{ns}:user:{uid}:blocked' +RL_NS_WINDOW = 'rl:{ns}:ip:{ip}:w:{bucket}' # --------------------------------------------------------------------------- # Bot UA fragments @@ -95,9 +96,9 @@ def get_client_ip(request): ip = x_forwarded_for.split(',')[0].strip() else: ip = ( - request.META.get('HTTP_X_REAL_IP') - or request.META.get('REMOTE_ADDR') - or '0.0.0.0' + request.META.get('HTTP_X_REAL_IP') + or request.META.get('REMOTE_ADDR') + or '0.0.0.0' ) return ip_mask(ip)