10.11. HTTPS – 서버를 위한 전송 암호화

지금까지는 모든 것이 포트 80의 평문 HTTP였습니다. 브라우저와 카메라 사이의 패킷을 캡처할 수 있는 사람이라면 누구나 로그인 폼의 비밀번호, 그로부터 돌아오는 세션 쿠키, Authorization 헤더의 JWT, 그리고 캡처된 모든 프레임의 JPEG 바이트를 읽을 수 있습니다. HTTPS는 통신을 종단 간 암호화합니다.

인증서 작업 흐름 자체, 즉 개발용 자체 서명 인증서 생성, 운영용 CA 서명 인증서 발급, 파일을 올바른 형식으로 카메라에 복사하는 과정은 TLS 인증서 다루기 에서 다룹니다. 이 페이지는 이미 로드된 인증서를 microdot에 연결하는 것 에 관한 내용입니다.

10.11.1. SSL 컨텍스트 구성하기

ssl.SSLContext 는 인증서, 키, 프로토콜 옵션을 담는 표준 라이브러리의 컨테이너입니다. 서버라면 PROTOCOL_TLS_SERVER 와 카메라의 파일 시스템에서 로드한 인증서 체인이 필요합니다:

import ssl

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain('/flash/cert.der', '/flash/key.der')

DER 경로는 TLS 섹션에서 실행한 작업 흐름에서 나옵니다. 개발용은 자체 서명, 운영용은 CA 서명입니다. 파일이 반드시 /flash 에 있어야 하는 것은 아니며 /sdcard 도 똑같이 잘 동작합니다.

10.11.2. start_server에 컨텍스트 전달하기

앞서의 start_server() 호출과 유일한 차이점은 ssl=ctx 인자와 포트 번호입니다. 포트 443은 HTTPS 기본값이므로 브라우저가 :443 을 입력할 필요가 없으며, 그냥 https://yard-cam.local/ 로 동작합니다:

async def main():
    await asyncio.gather(
        capture_loop(),
        motion_detector(),
        app.start_server(host='0.0.0.0', port=443, ssl=ctx),
    )

asyncio.run(main())

서버 측은 이것으로 끝입니다. 기존의 모든 라우트, 즉 /status, /snapshot.jpg, /stream.jpg, /config, /events, /control, 정적 대시보드가 이제 다른 코드 변경 없이 TLS 위에서 동작합니다.

10.11.4. 여기서 다루지 않는 것

HTTP Strict Transport Security (HSTS), 인증서 자동 갱신, 아웃바운드 요청을 위한 카메라 측의 CA 신뢰 체인, 암호 스위트 선택은 모두 TLS 섹션에 있습니다. 여기서의 연결 작업, 즉 하나의 SSLContext 와 하나의 ssl=ctx 만이 microdot에 특화된 유일한 부분입니다.

대시보드 URL 표시줄은 이제 http:// 에서 https:// 로 바뀌고 WebSocket은 ws:// 에서 wss:// 로 바뀝니다. 대시보드 JavaScript는 이미 location.protocol 을 기반으로 올바른 스킴을 선택했으므로 클라이언트 변경은 필요하지 않습니다.

카메라가 HTTPS로 서비스합니다. 로그인 폼, JWT, 캡처된 프레임이 전송 중에 암호화됩니다.