10.12. CORS e CSRF

CORS e CSRF são as duas proteções do lado do navegador que uma câmera aberta à internet precisa, junto com HTTPS e login. Cada uma leva algumas linhas para configurar. As seções abaixo definem o termo e mostram a integração com o microdot.

10.12.1. O que o CORS faz

Cross-Origin Resource Sharing (CORS) é o mecanismo do navegador que permite que um servidor opte por deixar origens específicas lerem suas respostas. A política de mesma origem padrão do navegador bloqueia essa leitura: JavaScript em https://example.com não pode ler respostas de https://yard-cam.example.com, porque um host diferente conta como origem diferente. O CORS é a forma, do lado do servidor, de conceder exceções a pares escolhidos.

Se o painel é servido pela própria câmera, toda requisição é de mesma origem e o CORS não faz nada. A configuração importa quando o painel reside em outro lugar – uma URL pública como https://app.example.com que conversa com uma câmera em 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 é a lista de origens autorizadas a ler as respostas da câmera. A origem do painel e apenas a origem do painel – não * – para que um site de terceiros não possa ler as respostas da câmera por acidente.

allow_credentials=True permite que requisições de origem cruzada incluam o cookie de sessão, que é o que o painel precisa para permanecer logado através de uma fronteira de origem.

max_age=86400 diz ao navegador que ele pode armazenar em cache o resultado do preflight por um dia. Os navegadores disparam uma requisição OPTIONS extra antes de qualquer chamada de origem cruzada que use métodos diferentes de GET/HEAD/POST ou que envie cabeçalhos personalizados; o max_age reduz essa sobrecarga a um preflight por dia por rota.

10.12.2. O que o CSRF faz

Cross-Site Request Forgery (CSRF) é o ataque em que uma página maliciosa faz o navegador do usuário disparar uma requisição autenticada contra um servidor confiável. Mesmo com o CORS no lugar, um <form> oculto em evil.com que faz POST para https://yard-cam.example.com/config chegará à câmera, e o navegador anexará o cookie de sessão da câmera – os cookies seguem o host de destino, não a origem da página que faz a requisição – de modo que a câmera processa alegremente o POST como se fosse o proprietário.

A proteção CSRF rejeita essas requisições. microdot.csrf.CSRF adiciona um middleware que inspeciona o cabeçalho Sec-Fetch-Site em toda requisição que altera estado e rejeita qualquer coisa não rotulada como same-origin (ou que venha de uma origem permitida pelo CORS):

from microdot.csrf import CSRF

CSRF(app, cors=cors)

Passar a instância cors permite que o middleware abra exceção para a origem permitida do painel – a câmera ainda aceita POSTs do painel mesmo que sejam de origem cruzada.

O Sec-Fetch-Site é definido automaticamente pelos navegadores modernos; a câmera não precisa fazer nada do lado do cliente. Para navegadores mais antigos que não enviam o cabeçalho, a lista de permissões do CORS é a verificação de reserva.

10.12.3. Isentando webhooks

Se a câmera precisar de um endpoint de webhook para aceitar POSTs de um serviço de nuvem de terceiros – um callback do provedor de arquivamento, por exemplo – marque a rota com @csrf.exempt para que o middleware a deixe passar. O handler é responsável por verificar a requisição de outra forma – geralmente um Hash-based Message Authentication Code (HMAC) sobre o payload, calculado com um segredo que a câmera e o terceiro compartilham, o que prova que a requisição veio de alguém que conhecia o segredo. A câmera do quintal não tem nada disso, mas o decorador está lá para quando você precisar.

10.12.4. A base de quatro linhas

Uma vez que o HTTPS esteja no lugar, a pilha recomendada para qualquer implantação de câmera voltada à internet é:

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)

Sessão e login no início do capítulo, CORS e CSRF aqui, HTTPS do tópico anterior. As quatro peças se empilham umas sobre as outras e ficam fora do caminho de cada rota.

A câmera agora está segura para se expor à internet aberta – HTTPS, login, CSRF, CORS.