DEV Community

Cover image for 로컬 FastAPI 관리 패널, Cloudflare Tunnel과 Access로 안전하게 외부 노출하고 보안 강화하기
바람의평온
바람의평온

Posted on

로컬 FastAPI 관리 패널, Cloudflare Tunnel과 Access로 안전하게 외부 노출하고 보안 강화하기

로컬 FastAPI 관리 패널, Cloudflare Tunnel과 Access로 안전하게 외부 노출하고 보안 강화하기

로컬 FastAPI 관리 패널, Cloudflare Tunnel과 Access로 안전하게 외부 노출하고 보안 강화하기

아들과 주말마다 공원이며 캠핑을 다니는 즐거움은 말로 다 표현하기 어렵습니다. 그런데 가끔 집을 비울 때, 로컬에 띄워둔 FastAPI 기반의 관리 패널이 눈에 밟힐 때가 있더군요. 개발 중인 서비스의 상태를 확인하거나 간단한 설정을 변경할 일이 생기면, 꼭 집에 돌아와야만 한다는 점이 아쉽게 느껴졌습니다. 처음에는 간단히 포트 포워딩을 생각해보기도 했습니다. 공유기 설정을 만져 8770 포트를 외부로 열어두면 되겠지 싶었죠. 하지만 민감한 관리 패널을 단순히 포트 포워딩으로 외부에 노출하는 것은 보안상 너무나 위험한 일이었습니다. 고정 IP가 없는 유동 IP 환경에서 접속 주소가 계속 바뀌는 것도 번거로운 일이고요. 괜히 이런 허술한 방법으로 외부 공격에 노출될까 봐 불안한 마음이 들었습니다. 주말에 아들과 즐겁게 시간을 보내면서도, 한편으로는 안전하게 외부에서 로컬 서비스를 관리할 방법에 대한 고민이 머릿속을 떠나지 않았습니다.

이런 분께 — 로컬에서 개발한 웹 서비스를 안전하게 외부로 노출하고 싶은 개발자, 특히 1인 개발자나 소규모 팀 개발자들에게 유용할 것입니다. · 난이도는 중급 정도

이번에 정리한 내용

  • 로컬에서 실행되는 웹 서비스를 외부로 안전하게 노출하는 방법
  • Cloudflare Tunnel을 활용한 포트 포워딩 없는 외부 접근 설정
  • Cloudflare Access로 이메일 OTP 기반의 인증 게이트 구축
  • FastAPI 백엔드에서 Cloudflare Access JWT를 검증하여 보안 강화
  • 민감한 터널 설정 파일을 안전하게 관리하는 팁

집 밖에서도 아들 재울 때 관리 패널을 열어볼까?

아들과 주말마다 공원이며 캠핑을 다니는 즐거움은 말로 다 표현하기 어렵습니다. 그런데 가끔 집을 비울 때, 로컬에 띄워둔 FastAPI 기반의 관리 패널이 눈에 밟힐 때가 있더군요. 개발 중인 서비스의 상태를 확인하거나 간단한 설정을 변경할 일이 생기면, 꼭 집에 돌아와야만 한다는 점이 아쉽게 느껴졌습니다. 처음에는 간단히 포트 포워딩을 생각해보기도 했습니다. 공유기 설정을 만져 8770 포트를 외부로 열어두면 되겠지 싶었죠. 하지만 금세 머릿속에서 경고음이 울렸습니다. 민감한 관리 패널을 단순히 포트 포워딩으로 외부에 노출하는 것은 보안상 너무나 위험한 일이었습니다. 고정 IP가 없는 유동 IP 환경에서 접속 주소가 계속 바뀌는 것도 번거로운 일이고요. 괜히 이런 허술한 방법으로 외부 공격에 노출될까 봐 불안한 마음이 들었습니다. 주말에 아들과 즐겁게 시간을 보내면서도, 한편으로는 안전하게 외부에서 로컬 서비스를 관리할 방법에 대한 고민이 머릿속을 떠나지 않았습니다.

보안과 유연성을 고려하니 Cloudflare Tunnel이 답이더군요

기본적인 포트 포워딩으로는 안 되겠다 싶어 다른 방법을 찾아보기 시작했습니다. VPN을 구축하는 것도 방법이겠지만, 간단한 관리 패널 접근을 위해 VPN 서버를 따로 운영하는 건 과하다고 생각했지요. 그러다 문득 Cloudflare Tunnel이 떠올랐습니다. 이전에 다른 프로젝트에서 잠깐 사용해본 경험이 있었는데, 외부에서 직접 서버 포트를 열 필요 없이 Cloudflare의 엣지 네트워크를 통해 안전하게 로컬 서비스를 노출할 수 있다는 점이 매력적이었습니다. Cloudflare Tunnel은 로컬에서 실행되는 cloudflared 데몬이 Cloudflare 네트워크와 보안 터널을 생성하고, 이 터널을 통해 외부 요청이 로컬 서비스로 전달되는 방식입니다. 이렇게 하면 외부에서 직접 제 공유기나 서버의 IP 주소를 알 필요도 없고, 특정 포트를 열어둘 필요도 없으니 보안 관점에서 훨씬 유리합니다. 무엇보다 유동 IP 환경에서도 Cloudflare가 제공하는 도메인을 통해 안정적으로 접근할 수 있다는 점이 제 상황에 딱 맞았습니다. 관리의 용이성이나 내구성을 따져봐도 다른 대안들보다 훨씬 실용적이라고 판단했습니다.

cloudflared로 로컬 FastAPI 서비스 연결하기

Cloudflare Tunnel을 사용하기로 결정한 후, 가장 먼저 할 일은 cloudflared 클라이언트를 설치하고 터널을 생성하는 것이었습니다. cloudflared는 macOS 환경에서 Homebrew로 쉽게 설치할 수 있었습니다. 터널을 생성한 다음, 로컬 FastAPI 서비스가 실행 중인 8770 포트를 Cloudflare 도메인과 연결하는 설정 파일을 만들어야 했습니다. 이 설정 파일은 YAML 형식으로 작성되며, 어떤 도메인으로 들어오는 요청을 로컬의 어떤 서비스로 연결할지 정의합니다. 예를 들어, your-domain.com으로 들어오는 요청을 http://localhost:8770으로 보내도록 설정하는 식이지요.

이렇게 설정 파일을 작성하고 나면, 다음 명령어로 터널을 실행할 수 있습니다.

cloudflared tunnel run YOUR_TUNNEL_NAME
Enter fullscreen mode Exit fullscreen mode

이 명령을 실행하면 cloudflared 데몬이 백그라운드에서 실행되며, Cloudflare 네트워크와 연결된 보안 터널을 유지합니다. 이때 중요한 점은 이 설정 파일에 터널 ID나 인증 관련 민감 정보가 포함될 수 있으므로, Git 저장소에 올릴 때는 반드시 .gitignore에 추가하여 버전 관리 대상에서 제외해야 한다는 것입니다. 저는 이 부분을 초기 실수로 놓칠 뻔했다가 아차 싶어 바로 .gitignore에 추가했습니다. 설정 파일의 내용은 다음과 같이 구성했습니다.

version: 0.0
hostname: your-domain.com
tunnel: YOUR_TUNNEL_UUID
metrics: 0.0.0.0:2006

ingress:
  - service: http://localhost:8770
  - service: http_status:404
Enter fullscreen mode Exit fullscreen mode

여기서 your-domain.com은 제가 연결하고자 하는 도메인이고, http://localhost:8770은 로컬 FastAPI 관리 패널이 실행되는 주소입니다. 마지막 http_status:404는 위의 규칙에 해당하지 않는 모든 요청에 대해 404 에러를 반환하도록 하는 기본 규칙입니다. 이렇게 설정하니 제 도메인으로 접속했을 때 로컬 FastAPI 서비스가 외부에서 잘 보이는 것을 확인할 수 있었습니다. 물론 아직 보안 장치는 없는 상태였죠.

Cloudflare Access로 보안의 문을 걸어 잠그다

로컬 서비스가 외부로 노출되는 것을 확인했으니, 이제는 접근을 통제할 차례였습니다. 단순 노출만으로는 민감한 관리 패널을 안전하게 사용할 수 없기 때문입니다. Cloudflare Access는 이럴 때 아주 유용한 도구입니다. 특정 도메인에 대한 접근을 제한하고, 사용자 인증을 통과해야만 서비스에 접근할 수 있도록 해주는 보안 게이트웨이 역할을 합니다. 저는 이메일 OTP(One-Time Password) 방식을 사용하여 저만 접근할 수 있도록 설정했습니다.

Cloudflare 대시보드에서 Access 애플리케이션을 생성하고, 정책을 추가했습니다. 정책 설정은 간단합니다. 특정 도메인(your-domain.com)에 접근할 때, 미리 지정한 이메일 주소(제 개인 이메일)로 OTP를 보내고, 그 OTP를 입력해야만 통과시키는 방식입니다. 이렇게 설정하고 나니, 이제 제 도메인으로 접속하면 Cloudflare Access의 로그인 페이지가 먼저 나타나더군요. 등록된 이메일 주소를 입력하면 해당 이메일로 OTP가 전송되고, 그 코드를 웹페이지에 입력해야만 비로소 FastAPI 관리 패널에 접근할 수 있게 되었습니다. 이 정도면 웬만한 무단 접근 시도는 막을 수 있겠다는 안도감이 들었습니다. 휴대폰으로 OTP를 확인하고 입력하는 과정이 아주 잠깐 번거롭긴 하지만, 그만큼 얻는 보안의 가치가 훨씬 컸습니다. 공원 벤치에 앉아있다가도, 아들이 잠든 캠핑장 텐트 안에서도 안심하고 관리 패널에 접속할 수 있게 된 것이죠. 혹시 모를 상황에 대비해 든든한 방어막을 친 기분입니다.

백엔드에서 JWT를 다시 확인하는 이중 방어

Cloudflare Access로 1차 보안을 구축했지만, 여기서 한 발 더 나아가기로 했습니다. 만약 어떤 이유로든 Cloudflare Tunnel을 우회하거나 Access 정책이 제대로 작동하지 않는 상황이 발생한다면 어떻게 될까요? 직접적인 IP 주소는 숨겨져 있지만, 만약의 사태에 대비한 이중 방어가 필요하다고 생각했습니다. Cloudflare Access는 인증에 성공하면 요청 헤더에 JWT(JSON Web Token)를 추가하여 서비스로 전달해줍니다. 저는 이 JWT를 FastAPI 백엔드에서 다시 한번 검증하는 미들웨어를 추가하여, 터널을 우회한 직접 접근을 원천적으로 차단하기로 했습니다.

FastAPI 애플리케이션에 미들웨어를 추가하여 모든 요청이 들어올 때마다 Cloudflare Access에서 발급한 JWT 헤더가 유효한지 확인하도록 했습니다. 유효하지 않은 JWT가 발견되면, 즉시 401 Unauthorized 응답을 반환하여 접근을 거부하는 방식입니다. 이렇게 하면 Access를 통과하지 않은 요청은 결코 제 관리 패널에 도달할 수 없게 됩니다. 제가 작성한 미들웨어의 기본적인 구조는 다음과 같습니다.

from fastapi import FastAPI, Request, HTTPException
from fastapi.security import HTTPBearer

app = FastAPI()
security = HTTPBearer()

@app.middleware("http")
async def verify_cloudflare_access_jwt(request: Request, call_next):
    # JWT 검증 로직 구현 (예: JWT 헤더 유효성, 서명 등)
    # 유효하지 않으면 HTTPException(status_code=401, detail="Unauthorized")
    response = await call_next(request)
    return response
Enter fullscreen mode Exit fullscreen mode

물론 위 코드의 # JWT 검증 로직 구현 부분에는 실제 JWT의 서명 검증, 만료 시간 확인, 발급자 확인 등 구체적인 로직이 들어가야 합니다. Cloudflare Access에서 제공하는 공개 키를 사용하여 JWT의 유효성을 검증하는 것이 일반적인 방법입니다. 이 과정을 추가함으로써, 만에 하나 Cloudflare Access 자체에 문제가 생기거나, 혹은 터널 설정에 오류가 발생하여 외부에서 직접 로컬 서비스로 요청이 들어오는 상황이 발생하더라도, 백엔드에서 마지막 방어선 역할을 해주게 됩니다. 이렇게 이중으로 보안 장치를 마련하니 훨씬 더 마음이 놓이더군요. 체감상 안정성과 보안성이 크게 향상되었다고 느꼈습니다.

마무리

로컬에서만 사용하던 FastAPI 기반의 관리 패널을 Cloudflare Tunnel과 Access를 활용하여 안전하게 외부로 노출하고, 심지어 백엔드 단에서 JWT를 검증하는 이중 방어막까지 구축하는 과정을 상세히 기록해 보았습니다. 처음에는 단순히 포트 포워딩을 고민했던 것과 비교하면, 훨씬 견고하고 유연한 접근 제어 시스템을 갖추게 된 셈입니다. 이 덕분에 이제는 집 밖 어디에서든, 아들과 즐거운 시간을 보내는 중에도 안심하고 제 서비스의 상태를 확인하고 관리할 수 있게 되었습니다. 혹시 저처럼 로컬 서비스를 안전하게 외부에 노출해야 하는 분들이 계시다면, 이 방법이 좋은 대안이 될 것이라고 생각합니다. 저의 경험이 여러분의 개발 여정에 작은 도움이 되었기를 바랍니다.

Top comments (0)