Stack: DRF + Redis · Proyecto: TicketFlow Estado: Publicada — cierre del módulo de seguridad Prerrequisito: Lección 22 — OWASP
Objetivos
- Gestionar secretos sin que acaben en Git, logs ni imágenes de Docker (jerarquía de entornos y rotación).
- Implementar rate limiting real (token bucket con Redis) en login y checkout.
- Aplicar RGPD a TicketFlow: base legal, minimización, derechos (export/borrado) y logs de auditoría.
1. Secretos: el ciclo de vida
- Nunca en Git (la 05):
.envfuera,env.exampledentro. El historial filtrado se repara rotando, no borrando. - Jerarquía de entorno: dev (valores dummy), staging (secretos de test), prod (KMS/vault: AWS Secrets Manager, HashiCorp Vault, o al mínimo: variables de entorno del orquestador — jamás baked en la imagen Docker: la imagen se filtra).
- Rotación: toda credencial tiene fecha de caducidad en tu inventario (
SECRET_KEYde Django: rota con plano de migración de sesiones; claves de pasarela: rotación dual — publicar nueva, migrar, revocar vieja). La rotación exitosa es la que puedes hacer sin downtime: diseña para eso. - Acceso de personas: nadie guarda secretos de prod en su máquina; acceso temporal, auditado (25/56).
git-secrets/trufflehogen CI para prevenir commits de secretos.
2. Rate limiting: token bucket con Redis
El límite protege: login (brute force, 20), checkout (bots comprando todo — el caso I4-adjacente de TicketFlow), API pública (abuso). Algoritmo token bucket: el usuario tiene N tokens que se regeneran a ritmo R; cada request gasta uno; sin token → 429 con Retry-After.
def allow(key: str, capacity: int, refill_per_min: int) -> bool:
now = time.time()
pipe = redis_pipeline()
data = pipe.hgetall(key).execute() # tokens, last_refill
tokens = min(capacity, float(data.get(b"tokens", capacity)))
elapsed = now - float(data.get(b"last", now))
tokens = min(capacity, tokens + elapsed * refill_per_min / 60)
if tokens < 1:
pipe.hset(key, mapping={"tokens": tokens, "last": now}); pipe.expire(key, 3600)
return False
pipe.hset(key, mapping={"tokens": tokens - 1, "last": now}); pipe.expire(key, 3600)
return TrueProducción: hazlo atómico con un script Lua (evita la raza entre HGETALL y HSET — la 10/12 aplicada a Redis) o usa la librería django-ratelimit. Claves: por IP para anónimos, por usuario para autenticados, por endpoint sensible (login, checkout) con límites más duros. Y respuesta honesta: 429 + Retry-After + log del evento (46): un bot bloqueado es información, no ruido.
3. RGPD para el desarrollador (lo que toca tu código)
- Base legal y minimización: solo datos necesarios para vender entradas: nombre, email, y los de facturación. El "fecha de nacimiento por si acaso" NO se recoge. Menos datos = menos responsabilidad.
- Finalidad y retención: las reservas se retienen (fiscal), los carritos abandonados NO: job de purga de PENDING expiradas viejas (anónimas) — la retención es una decisión de datos, documentada.
- Derecho de acceso/export (Art. 15): endpoint de export del usuario (su perfil + reservas + pagos, JSON o CSV) — autenticado, su propio dato, la 14 garantiza idempotencia del export.
- Derecho al olvido (Art. 17): borrar/no: las ventas tienen obligación fiscal → anonimización (email → hash irreversibles, nombre → "eliminado"), mantener el registro contable. Nunca DELETE de pagos (auditoría, la 08).
- Consentimiento: marketing separado de la operación (checkbox no obligatorio), revocable en un clic.
- Procesadores: la pasarela y el proveedor de email son encargados de tratamiento: DPA firmado (papel del negocio; tu parte: saber qué datos viajan a cada uno).
4. Logs de auditoría (la puntada que une)
El audit log registra QUIÉN hizo QUÉ CUÁNDO sobre qué recurso, inmutable: cambios de rol (21), reembolsos, cambios de evento, accesos a datos de usuario. Tabla append-only (sin UPDATE/DELETE por diseño) + retención definida. Es lo que convierte "creemos que nadie tocó el pago" en "aquí está el registro" (la 56 lo formaliza para SOC2).
Autoevaluación
- Filtró tu repo un secreto de la pasarela hace un mes: ¿cuál es la única reparación real y cómo se hace sin downtime?
- ¿Por qué el token bucket necesita atomicidad (Lua) y qué bug produce la versión con HGET/HSET separados?
- ¿Por qué el rate limit del login es por usuario+IP y no solo por IP? ¿Qué ataque evita el límite por usuario?
- Un usuario pide borrarse: ¿por qué no DELETE de sus pagos y qué se hace exactamente con sus datos?
- ¿Qué tres datos NO debe pedir TicketFlow y por qué (minimización)?
Continúa con los ejercicios. Las solutions.md solo tras intentarlo.