10.12. CORS y CSRF

CORS y CSRF son las dos protecciones del lado del navegador que una cámara expuesta a internet abierto necesita junto con HTTPS y el inicio de sesión. Cada una requiere unas pocas líneas para configurarse. Las secciones siguientes definen el término y muestran la integración con microdot.

10.12.1. Qué hace CORS

El Intercambio de Recursos de Origen Cruzado (Cross-Origin Resource Sharing, CORS) es el mecanismo del navegador que permite a un servidor optar por dejar que otros orígenes concretos lean sus respuestas. La política del mismo origen predeterminada del navegador bloquea esa lectura: el JavaScript en https://example.com no puede leer respuestas de https://yard-cam.example.com, porque un host distinto cuenta como un origen distinto. CORS es la forma, del lado del servidor, de conceder excepciones para pares elegidos.

Si el panel de control se sirve desde la propia cámara, cada solicitud es del mismo origen y CORS no hace nada. La configuración importa cuando el panel de control reside en otro lugar: una URL pública como https://app.example.com que se comunica con una cámara en https://yard-cam.example.com:

from microdot.cors import CORS

cors = CORS(
    app,
    allowed_origins=['https://app.example.com'],
    allow_credentials=True,
    max_age=86400,
)

allowed_origins es la lista de orígenes a los que se permite leer las respuestas de la cámara. El origen del panel de control y solo el origen del panel de control – no * – para que un sitio de terceros no pueda leer por accidente las respuestas de la cámara.

allow_credentials=True permite que las solicitudes de origen cruzado incluyan la cookie de sesión, que es lo que el panel de control necesita para permanecer con la sesión iniciada a través de una frontera de origen.

max_age=86400 indica al navegador que puede almacenar en caché el resultado de la verificación previa (preflight) durante un día. Los navegadores disparan una solicitud OPTIONS adicional antes de cualquier llamada de origen cruzado que use métodos distintos de GET/HEAD/POST o que envíe cabeceras personalizadas; max_age reduce esa sobrecarga a una verificación previa por día y por ruta.

10.12.2. Qué hace CSRF

La Falsificación de Solicitud entre Sitios (Cross-Site Request Forgery, CSRF) es el ataque en el que una página maliciosa hace que el navegador del usuario dispare una solicitud autenticada contra un servidor de confianza. Incluso con CORS en su sitio, un <form> oculto en evil.com que hace POST a https://yard-cam.example.com/config llegará a la cámara, y el navegador adjuntará la cookie de sesión de la cámara – las cookies siguen al host de destino, no al origen de la página que hace la solicitud – de modo que la cámara procesa alegremente el POST como si fuera el propietario.

La protección CSRF rechaza esas solicitudes. microdot.csrf.CSRF añade un middleware que inspecciona la cabecera Sec-Fetch-Site en cada solicitud que cambia el estado y rechaza todo lo que no esté etiquetado como same-origin (o que provenga de un origen permitido por CORS):

from microdot.csrf import CSRF

CSRF(app, cors=cors)

Pasar la instancia cors permite que el middleware incluya por antigüedad (grandfather) el origen permitido del panel de control: la cámara sigue aceptando los POST del panel aunque sean de origen cruzado.

Sec-Fetch-Site lo establecen automáticamente los navegadores modernos; la cámara no tiene que hacer nada en el lado del cliente. Para los navegadores antiguos que no envían la cabecera, la lista de permitidos de CORS es la comprobación de respaldo.

10.12.3. Eximir webhooks

Si la cámara necesita un endpoint de webhook para aceptar POST de un servicio en la nube de terceros – una devolución de llamada del proveedor del archivo, por ejemplo – marca la ruta con @csrf.exempt para que el middleware la deje pasar. El manejador es responsable de verificar la solicitud de alguna otra manera: normalmente un Código de Autenticación de Mensaje basado en Hash (Hash-based Message Authentication Code, HMAC) sobre la carga útil, calculado con un secreto que la cámara y el tercero comparten, lo que demuestra que la solicitud provino de alguien que conocía el secreto. La cámara del patio trasero no tiene nada de eso, pero el decorador está ahí para cuando lo necesites.

10.12.4. La base de cuatro líneas

Una vez que HTTPS está en su sitio, la pila recomendada para cualquier despliegue de cámara expuesta a internet es:

Session(app, secret_key=SECRET,
        cookie_options={'http_only': True, 'secure': True})
login = Login()
cors = CORS(app, allowed_origins=[...], allow_credentials=True)
CSRF(app, cors=cors)

Sesión e inicio de sesión antes en el capítulo, CORS y CSRF aquí, HTTPS del tema anterior. Las cuatro piezas se apilan unas sobre otras y se mantienen al margen de cada ruta.

La cámara es ahora segura para enfrentarse a internet abierto: HTTPS, inicio de sesión, CSRF, CORS.