10.12. CORS e CSRF

CORS e CSRF são as duas proteções do lado do browser que uma câmara exposta à internet necessita, a par do HTTPS e da autenticação. Cada uma requer apenas algumas linhas de configuração. As secções abaixo definem os termos e mostram a integração com o microdot.

10.12.1. O que é o CORS

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

Se o painel de controlo for servido a partir da própria câmara, todos os pedidos têm a mesma origem e o CORS não faz nada. A configuração é importante quando o painel reside noutro local – um URL público como https://app.example.com que comunica com uma câmara 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âmara. A origem do painel de controlo e apenas a origem do painel – não * – para que um site de terceiros não possa ler as respostas da câmara acidentalmente.

allow_credentials=True permite que pedidos de origens cruzadas incluam o cookie de sessão, que é o que o painel de controlo precisa para permanecer autenticado através de uma fronteira de origens.

max_age=86400 indica ao browser que pode guardar em cache o resultado do preflight durante um dia. Os browsers enviam um pedido OPTIONS extra antes de qualquer chamada entre origens que utilize métodos diferentes de GET/HEAD/POST ou envie cabeçalhos personalizados; max_age reduz essa sobrecarga a um preflight por dia por rota.

10.12.2. O que é o CSRF

Cross-Site Request Forgery (CSRF) é o ataque em que uma página maliciosa faz com que o browser do utilizador envie um pedido autenticado a um servidor de confiança. Mesmo com CORS ativo, um <form> oculto em evil.com que faça POST para https://yard-cam.example.com/config chegará à câmara, e o browser anexará o cookie de sessão da câmara – os cookies seguem o host de destino, não a origem da página que faz o pedido – pelo que a câmara processa alegremente o POST como se fosse do proprietário.

A proteção CSRF rejeita esses pedidos. microdot.csrf.CSRF adiciona middleware que inspeciona o cabeçalho Sec-Fetch-Site em cada pedido que altera estado e rejeita tudo o que não esteja marcado como same-origin (ou proveniente de uma origem permitida por CORS):

from microdot.csrf import CSRF

CSRF(app, cors=cors)

Passar a instância cors permite ao middleware considerar a origem permitida do painel – a câmara continua a aceitar POSTs do painel mesmo que sejam de origens cruzadas.

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

10.12.3. Isentar webhooks

Se a câmara precisar de um endpoint de webhook para aceitar POSTs de um serviço de nuvem de terceiros – por exemplo, um callback do fornecedor de arquivo – marque a rota @csrf.exempt para que o middleware a deixe passar. O handler é responsável por verificar o pedido de outra forma – normalmente um Hash-based Message Authentication Code (HMAC) sobre o payload, calculado com um segredo partilhado entre a câmara e o terceiro, o que prova que o pedido veio de alguém que conhecia o segredo. A câmara do jardim não tem nenhum desses, mas o decorador está disponível quando precisar.

10.12.4. A base de quatro linhas

Com HTTPS configurado, a pilha recomendada para qualquer implementação de uma câmara voltada para a 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 autenticação mais cedo no capítulo, CORS e CSRF aqui, HTTPS no tópico anterior. As quatro peças empilham-se umas sobre as outras e não interferem com o código de cada rota.

A câmara está agora segura para enfrentar a internet aberta – HTTPS, autenticação, CSRF, CORS.