RSS듀오랩스
보안

업무 시스템 보안 구성: MFA, 세션, WAF, RBAC, RLS

작성자
듀오랩스 대표·11분 읽기

사내 업무 시스템을 만들 때 자주 나오는 질문이 있습니다. 「앞에 Cloudflare Access 같은 걸 두면 보안은 끝나는 것 아닌가요?」

회사 전용 도구라면 이 방향이 맞을 때가 있습니다. Cloudflare Access 의 정책 문서가 설명하는 대로, 애플리케이션 앞에 신원 기반 관문을 두고 정책으로 누가 들어올 수 있는지를 정하는 방식입니다. 로그인하지 않은 요청은 앱에 닿지도 않습니다.

그런데 이 관문은 앱 앞의 한 층입니다. 통과한 사람이 앱 안에서 무엇을 할 수 있는지, 통과한 세션이 얼마나 오래 유효한지, 통과한 사용자가 남의 데이터를 조회하는 요청을 보낼 때 무엇이 막는지는 여전히 앱과 데이터베이스가 정합니다. 사용자가 많지 않은 사내 업무 시스템이라면, 저는 관문을 하나 더 세우기 전에 아래 네 층을 제대로 만드는 쪽에 먼저 투자하겠습니다.

보안을 「앞단 게이트 하나」로 보는 시각

게이트 모델이 매력적인 이유는 분명합니다. 한 곳만 설정하면 되고, 통과 여부가 눈에 보이고, 도입이 빠릅니다. 그래서 보안 요구가 생기면 앞단에 무언가를 더하는 것부터 떠올리게 됩니다.

이 모델이 설명하지 못하는 사고가 있습니다. 로그인한 직원이 남의 계정 데이터를 조회하는 경우, 퇴사자의 세션이 며칠 더 살아 있는 경우, 비밀번호가 유출되어 정상적인 로그인으로 들어오는 경우입니다. 셋 다 관문을 정상적으로 통과한 요청입니다.

그래서 층을 나눠 봐야 합니다. 층마다 막는 것이 다르고, 빠졌을 때의 피해도 다릅니다.

막는 것 빠졌을 때
네트워크 앞단 (WAF, Access) 무차별 요청, 알려진 공격 패턴, 외부인 접근 시끄럽고 비용이 들지만 데이터가 곧바로 새지는 않는다
인증 (MFA) 비밀번호만으로 들어오는 로그인 유출된 비밀번호 하나로 정상 로그인이 된다
세션 오래된 로그인, 탈취된 쿠키 한 번 로그인한 흔적이 오래 남는다
서버 권한 (RBAC) 로그인한 사람이 하면 안 되는 동작 내부 사용자가 권한 밖의 일을 한다
데이터베이스 권한 (RLS) 서버 코드의 실수로 새는 조회 코드 한 줄의 누락이 곧 데이터 유출이다

아래로 갈수록 사고가 조용하고 피해가 큽니다. 이런 규모의 시스템에서 현실적인 위험은 대부분 아래 세 층에 있습니다.

인증: MFA 를 어디까지 강제할까

OWASP 의 MFA 치트시트는 출발점으로 삼을 권고를 짧게 정리합니다. 모든 사용자에게 어떤 형태로든 MFA 를 요구하고, TOTP 로 MFA 를 켤 수 있게 하고, 관리자나 고권한 사용자에게는 MFA 를 필수로 하고, MFA 를 잃었을 때의 재설정 절차를 안전하게 만들라는 내용입니다.

같은 문서는 FIDO2 기반 패스키를 피싱에 강하면서도 사용자 부담이 적은 방식으로 소개합니다. 소규모 조직이라면 패스키를 기본으로 하고 TOTP 를 대비책으로 두는 조합이 현실적이라고 봅니다. 문자(SMS)는 문서가 장단점을 함께 적는 방식이라, 다른 수단을 쓸 수 있다면 우선순위를 낮추는 편이 낫다고 생각합니다.

작은 조직에서 자주 빠지는 것은 재설정 절차입니다. 휴대폰을 바꾼 직원의 MFA 를 대표가 관리 화면에서 그냥 꺼 주는 운영이 되면, 그 화면이 곧 우회로가 됩니다. 재설정도 본인 확인 절차와 기록을 남기는 동작이어야 합니다.

세션: 쿠키 속성과 세 가지 만료

OWASP 세션 관리 치트시트는 세션 쿠키에 Secure, HttpOnly, SameSite 속성을 두는 것부터 시작합니다. 자바스크립트가 세션 값을 읽지 못하게 하고, HTTPS 에서만 전송되게 하고, 다른 사이트에서 시작된 요청에 딸려 가지 않게 하는 설정입니다.

만료도 한 종류가 아닙니다. 문서는 자동 만료를 셋으로 나눕니다.

  • 유휴 만료: 일정 시간 활동이 없으면 끊는다
  • 절대 만료: 활동과 무관하게 정해진 시간이 지나면 끊는다
  • 갱신 만료: 세션은 유지하되 식별자를 주기적으로 새로 발급한다

업무 시스템에서 셋을 모두 정해 두면, 「퇴근하면서 열어 둔 브라우저」와 「몇 주째 로그인 상태인 계정」이 함께 정리됩니다.

한 가지 더, 문서는 권한 수준이 바뀔 때 세션 식별자를 새로 발급하라고 합니다. 로그인 직후가 대표적입니다. 역할이 바뀌거나 민감한 동작 전에 다시 인증하는 경우도 같습니다. 그리고 로그아웃 버튼은 클라이언트에서 쿠키를 지우는 것으로 끝나면 안 되고, 서버에서 세션을 무효화해야 합니다.

앞단: Vercel WAF 로 할 수 있는 것

Vercel WAF 의 커스텀 규칙 문서는 트래픽을 기록(log)하거나 차단(deny), 챌린지(challenge), 우회(bypass), 속도 제한(rate limit)하는 규칙을 만들 수 있다고 적습니다. 모든 요금제에서 쓸 수 있고, 규칙을 적용하면 재배포 없이 즉시 반영됩니다.

소규모 업무 시스템에서 이 기능이 값을 하는 곳은 화려한 공격 방어보다 다음 쪽이라고 봅니다.

  • 로그인과 비밀번호 재설정 경로에 속도 제한
  • 관리 화면 경로를 회사 IP 나 특정 조건으로 제한 (직원이 재택이면 이 조건은 신중히)
  • 평소에 오지 않아야 할 경로에 대한 요청을 기록해 두기

즉시 반영된다는 점은 사고 대응에서 중요합니다. 특정 IP 에서 로그인 시도가 쏟아질 때 배포를 기다리지 않고 막을 수 있습니다.

서버 권한: 화면을 숨기는 것은 권한이 아니다

여기서부터가 직접 만드는 층입니다. 역할 기반 권한(RBAC)의 핵심은 확인하는 위치입니다.

관리자에게만 「삭제」 버튼을 보여 주는 것은 사용자 경험이고, 권한 확인이 아닙니다. 서버 함수나 API 는 화면과 무관하게 호출될 수 있습니다. 서버 함수 글에서 다룬 대로, 서버 함수는 그것을 쓰는 페이지 경로로 가는 요청이라 페이지의 인증 확인이 자동으로 적용되지 않습니다.

그래서 서버 쪽 확인은 세 가지를 모두 봐야 합니다.

  1. 인증: 로그인한 사용자인가
  2. 권한: 이 역할이 이 동작을 할 수 있는가
  3. 소유: 이 사용자가 이 대상에 대해 할 수 있는가

세 번째가 자주 빠집니다. 로그인한 직원이 다른 회사, 다른 부서, 다른 사용자의 id 를 넣었을 때 막는 확인입니다. 이 확인을 데이터 접근 계층 한 곳에 모으면 새 화면을 만들 때마다 잊을 일이 줄어듭니다.

데이터베이스 권한: RLS 는 마지막 방어선

Supabase 를 쓴다면 그 아래 한 층이 더 있습니다. PostgreSQL 의 행 수준 보안(RLS)입니다. Supabase 의 RLS 문서는 몇 가지를 분명히 적습니다.

  • service_roleRLS 를 우회합니다. 그래서 이 키는 서버 쪽에만 두어야 합니다
  • 정책(policy)과 권한(grant)은 다릅니다. 정책을 추가해도 이미 준 grant 가 회수되지는 않으므로, 역할마다 필요한 동작만 grant 로 남깁니다
  • 정책은 select, insert, update, delete 각각 작성합니다. for all 하나로 묶으면 어느 동작을 의도한 규칙인지 숨겨집니다

RLS 를 켜면 앱 코드에서 조건을 빠뜨려도 데이터베이스가 남의 행을 돌려주지 않습니다. 서버 코드의 실수 하나가 곧 유출이 되는 구조에서, 실수의 결과를 「빈 목록」으로 바꿔 주는 층입니다.

제가 보기에 RLS 의 가치는 「코드를 믿지 않아도 되는 층이 하나 생긴다」는 데 있습니다. 대신 정책을 테스트하지 않으면 이 층은 있는지도 모르게 지나갑니다. 정책마다 「이 역할로 이 행이 보이면 안 된다」를 확인하는 테스트를 함께 두는 편이 맞다고 봅니다.

Cloudflare Access 가 맞는 경우

앞단 관문이 필요 없다는 이야기가 아닙니다. 이런 조건이라면 관문이 잘 맞습니다.

  • 회사 사람만 쓰는 도구이고, 외부 고객이 접근할 일이 전혀 없다
  • 사내 대시보드, 모니터링, 관리 도구처럼 인증 UI 를 직접 만들 이유가 없는
  • 외주 인력이나 계약직에게 기간을 정해 접근을 열고 닫아야 한다

반대로 고객이 쓰는 서비스, 즉 거래처나 소비자가 접속하는 시스템에는 앞단 관문을 씌울 수 없습니다. 그때는 앱 안의 인증과 권한이 유일한 방어선이 됩니다.

제가 잡는 순서

순서 할 일 이유
1 서버 권한 확인 (인증·권한·소유) 빠졌을 때 피해가 가장 크고, 코드로만 해결된다
2 세션 설정 (쿠키 속성, 세 가지 만료, 로그인 시 식별자 재발급) 설정 몇 줄로 위험이 크게 줄어든다
3 관리자 MFA 필수, 전원 MFA 권장 비밀번호 유출 하나가 곧 침입이 되는 것을 막는다
4 RLS (Supabase 를 쓴다면) 1번의 실수를 데이터베이스가 한 번 더 걸러 준다
5 WAF 규칙 (로그인 속도 제한, 이상 요청 기록) 즉시 적용되고 운영 중에도 조정할 수 있다
6 필요하면 앞단 관문 (Access) 사내 전용 도구일 때

순서를 이렇게 두는 이유는 하나입니다. 1번부터 4번까지는 우리가 만들지 않으면 아무도 만들어 주지 않는 층이고, 5번과 6번은 나중에 붙여도 붙는 층이기 때문입니다.

여기까지가 확실한 부분

MFA 권고(전원 MFA, TOTP 제공, 관리자 필수, 안전한 재설정)와 패스키 설명은 OWASP MFA 치트시트, 쿠키 속성과 세 가지 만료, 권한 변경 시 세션 식별자 재발급은 OWASP 세션 관리 치트시트 기준입니다. WAF 커스텀 규칙의 동작과 즉시 반영은 Vercel 문서, service_role 의 RLS 우회와 grant·정책의 관계, 동작별 정책 작성은 Supabase 문서, 신원 기반 접근 정책은 Cloudflare 문서 기준입니다. 어떤 조합이 「충분한지」는 다루는 데이터와 업종, 계약상 요구에 따라 달라지므로 이 글은 우선순위에 대한 판단까지만 적었습니다. 규제 대상 데이터를 다룬다면 해당 규정을 먼저 확인해야 합니다.

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.