10.12. CORS와 CSRF

CORSCSRF는 공개 인터넷에 노출된 카메라가 HTTPS 및 로그인과 함께 필요로 하는 두 가지 브라우저 측 보호 장치입니다. 각각 설정에 몇 줄이 필요합니다. 아래 섹션에서는 용어를 정의하고 microdot 통합 방법을 보여줍니다.

10.12.1. CORS가 하는 일

Cross-Origin Resource Sharing(CORS)은 서버가 특정 다른 오리진이 자신의 응답을 읽을 수 있도록 옵트인할 수 있게 해주는 브라우저 메커니즘입니다. 브라우저의 기본 동일 출처 정책(same-origin policy)은 그러한 읽기를 차단합니다: https://example.com의 JavaScript는 https://yard-cam.example.com의 응답을 읽을 수 없는데, 호스트가 다르면 다른 오리진으로 간주되기 때문입니다. CORS는 선택한 상대에게 예외를 부여하는 서버 측 방법입니다.

대시보드가 카메라 자체에서 제공되면 모든 요청이 동일 출처이므로 CORS는 아무 일도 하지 않습니다. 이 설정은 대시보드가 다른 곳에 있을 때 – https://yard-cam.example.com의 카메라와 통신하는 https://app.example.com 같은 공개 URL일 때 – 의미가 있습니다:

from microdot.cors import CORS

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

allowed_origins는 카메라의 응답을 읽도록 허용된 오리진 목록입니다. 대시보드의 오리진, 그리고 오직 대시보드의 오리진만 – *가 아니라 – 넣어, 제3자 사이트가 실수로 카메라의 응답을 읽지 못하게 합니다.

allow_credentials=True는 교차 출처 요청에 세션 쿠키를 포함할 수 있게 해주며, 이는 대시보드가 오리진 경계를 넘어 로그인 상태를 유지하는 데 필요한 것입니다.

max_age=86400은 브라우저에게 프리플라이트 결과를 하루 동안 캐시해도 된다고 알려줍니다. 브라우저는 GET/HEAD/POST 이외의 메서드를 사용하거나 커스텀 헤더를 보내는 모든 교차 출처 호출 전에 추가 OPTIONS 요청을 발생시키는데, max_age는 그 오버헤드를 라우트당 하루에 한 번의 프리플라이트로 줄여줍니다.

10.12.2. CSRF가 하는 일

Cross-Site Request Forgery (CSRF)는 악성 페이지가 사용자의 브라우저를 통해 신뢰된 서버로 인증된 요청을 보내게 만드는 공격입니다. CORS가 적용되어 있더라도, evil.com 에 숨겨진 <form>https://yard-cam.example.com/config 로 POST를 보내면 그 요청은 카메라에 도달하고, 브라우저는 카메라의 세션 쿠키를 첨부합니다 – 쿠키는 요청을 보내는 페이지의 출처가 아니라 목적지 호스트를 따르기 때문입니다 – 그래서 카메라는 그 POST를 마치 소유자가 보낸 것처럼 아무 의심 없이 처리하게 됩니다.

CSRF 보호는 그러한 요청을 거부합니다. microdot.csrf.CSRF는 상태를 변경하는 모든 요청에서 Sec-Fetch-Site 헤더를 검사하여 same-origin으로 표시되지 않은(또는 CORS가 허용한 오리진에서 온 것이 아닌) 모든 것을 거부하는 미들웨어를 추가합니다:

from microdot.csrf import CSRF

CSRF(app, cors=cors)

cors 인스턴스를 전달하면 미들웨어가 대시보드의 허용된 오리진을 예외로 인정해 줍니다 – 카메라는 대시보드의 POST가 교차 출처임에도 여전히 받아들입니다.

Sec-Fetch-Site는 최신 브라우저가 자동으로 설정하므로, 카메라는 클라이언트 측에서 아무것도 할 필요가 없습니다. 이 헤더를 보내지 않는 구형 브라우저의 경우 CORS 허용 목록이 대체 검사가 됩니다.

10.12.3. 웹훅 예외 처리

카메라가 서드파티 클라우드 서비스로부터 POST를 받는 웹훅 엔드포인트가 필요하다면 – 예를 들어 아카이브 제공자로부터의 콜백 같은 경우 – 해당 라우트를 @csrf.exempt 로 표시하여 미들웨어가 통과시키도록 합니다. 그러면 그 핸들러는 다른 방식으로 요청을 검증할 책임을 지게 됩니다 – 보통은 카메라와 서드파티가 공유하는 비밀 값으로 페이로드에 대해 계산하는 Hash-based Message Authentication Code (HMAC)를 사용하며, 이는 그 비밀 값을 알고 있는 사람이 요청을 보냈음을 증명합니다. 뒤뜰 카메라에는 이런 것들이 전혀 없지만, 필요할 때를 위해 이 데코레이터는 준비되어 있습니다.

10.12.4. 네 줄짜리 기본 구성

HTTPS가 적용되고 나면, 인터넷에 노출되는 모든 카메라 배포에 권장되는 스택은 다음과 같습니다:

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)

이 장의 앞부분에서 다룬 세션과 로그인, 여기서 다룬 CORS와 CSRF, 그리고 이전 주제에서 다룬 HTTPS. 이 네 조각은 서로의 위에 쌓이며 각 라우트의 작동을 방해하지 않습니다.

이제 카메라는 공개 인터넷에 안전하게 노출할 수 있습니다 – HTTPS, 로그인, CSRF, CORS.