
MCP 서버를 사내에 구축하기 전 결정한 3가지 —— 인증·역할·검사의 순서
요약
사내 MCP(Model Context Protocol) 서버 구축 시 보안을 위해 고려해야 할 인증, 역할, 검사 설계 원칙을 다룹니다. 인증 부재 시의 위험성과 권한 관리 시 '안전한 실패(Fail-safe)'를 지향하는 설계 방식을 제안합니다.
핵심 포인트
- 인증이 없어도 MCP는 정상 작동하므로 보안 취약점이 발생할 수 있음
- 권한 부족 메시지 대신 도구 미존재 응답을 통해 정보 노출 방지
- 권한 체크는 개별 도구가 아닌 등록 단계에서 중앙 집중식으로 관리
- 알 수 없는 역할(Role) 값은 반드시 가장 낮은 권한으로 처리
작동했다. 그런데, 이걸 어디에 둘 것인가
MCP 서버를 만들어 본 사람이라면, 공식 샘플을 작동시키는 단계까지는 아마 막힘없이 진행할 수 있을 것입니다. 문서대로 하면 작동합니다.
문제는 그다음이었습니다. 작동했다. 그런데, 이걸 어디에 둘 것인가.
둘 곳은 세 가지가 있습니다. 자신의 PC, 사내 서버, 인터넷. 자신의 PC에 두고 있는 한, 아무도 곤란해하지 않습니다. 자신만이 만질 수 있기 때문입니다.
하지만, 그것만으로는 업무에 사용할 수 없습니다. 사내 사람들이 사용하게 하려면 어딘가에 두어야 합니다.
그리고 배치하는 순간, 인증 (Authentication)을 설정하지 않았다는 사실을 깨닫게 됩니다.
인증이 없어도 에러는 발생하지 않는다
여기서 오해하기 쉬운 점이 있는데, 인증이 없어도 에러는 발생하지 않습니다. 제대로 작동합니다. 열쇠 없이 두드려도 200 OK가 반환되며, 도구 목록이 전부 보입니다. 고장 난 것이 아니기에 아무도 눈치채지 못합니다.
즉, 누구나 들어올 수 있는 상태입니다.
그리고 MCP에는 특유의 무서움이 있습니다. 일반적인 데이터베이스라면, 들어오더라도 SQL을 작성할 수 없다면 아무것도 꺼낼 수 없습니다. MCP는 AI가 연결되어 있으므로, 자연어 (Natural Language)로 정보를 끌어낼 수 있습니다. "지난달 매출은?"이라고 묻기만 하면 됩니다.
제가 실무에서 운영하고 있는 MCP 서버에는 읽기 전용 도구가 십수 개 있습니다. 그곳에는 경영에 관련된 숫자가 들어 있습니다.
경영 수치가 사내 누구에게나, 일본어로 묻는 것만으로 나온다. 그것이 바로 인증을 설정하지 않았을 때의 결과였습니다.
설계 판단 ① "권한이 없습니다"라고 답하지 않는다
권한이 없는 도구를 호출했을 때, 제 서버는 다음과 같이 응답합니다.
Tool server_config not found
"권한이 없습니다"가 아닙니다. 그런 도구는 없습니다라는 뜻입니다.
이유는 두 가지가 있습니다.
첫 번째. "권한이 없습니다"라고 답하면, 해당 도구가 존재한다는 사실이 상대에게 전달됩니다.
존재를 알게 되면, 다음으로는 "어떻게 하면 호출할 수 있는가"를 찾아 나섭니다. 헤더 (Header)를 조작하거나, 다른 열쇠를 시도합니다. 존재하지 않는 것은 노려지지 않습니다.
두 번째. 이것은 만드는 쪽의 이야기입니다.
권한 체크를 각 도구 내부에 작성하는 방식이라면, 도구가 늘어날수록 작성해야 할 곳도 늘어납니다. 10개라면 10곳입니다.
인간은 반드시 실수를 합니다. 그리고 까다로운 점은, 작성하는 것을 잊어도 에러가 발생하지 않는다는 것입니다. 그 도구만 누구나 사용할 수 있는 상태가 되며, 정상적으로 작동하고 있으므로 아무도 눈치채지 못합니다. 서두에서 언급한 것과 같은 구조입니다.
등록 단계에서 분류하면, 작성할 곳은 roles: ["admin"] 한 줄뿐입니다. 만약 작성을 잊는다면, 그 도구는 누구에게도 보이지 않게 됩니다.
작성을 잊었을 때 위험한 쪽으로 기울 것인가, 안전한 쪽으로 기울 것인가. 의도하지 않았더라도, 방식을 결정한 시점에서 이미 결정되어 있습니다.
설계 판단 ② 모르는 값이 오면 반드시 약한 쪽으로 기울인다
역할 (Role)은 X-User-Role이라는 헤더로 받습니다. 여기에 들어오는 값은 항상 예상대로라고 할 수 없습니다. 비어 있을 수도 있고, 대소문자가 다를 수도 있으며, 애초에 헤더가 없을 수도 있습니다.
이때 "모르는 값이니 admin으로 설정하자"라고 만들면 다음과 같은 상황이 벌어집니다.
공격하는 쪽이 할 일은 헤더를 보내지 않는 것뿐입니다.
아무것도 추측하지 않고, 아무것도 파괴하지 않고, 단 한 줄을 보내지 않은 것만으로 관리자가 될 수 있습니다. "아무것도 하지 않는 것"이 최강의 공격이 되는 설계입니다.
따라서 모르는 값은 반드시 viewer (최소 권한) 쪽으로 기울게 합니다. 무엇을 보내든, 무엇을 보내지 않든, 약한 권한밖에 가질 수 없게 합니다.
여기까지 쓰다 보니 깨달은 점인데, 설계 판단 ①과 ②는 같은 이야기를 하고 있습니다.
①은 "작성을 잊었을 때", ②는 "모르는 값이 왔을 때". 둘 다 예상치 못한 일이 발생했을 때, 어느 쪽으로 기울 것인가에 대한 이야기였습니다.
예상 범위 내의 일을 올바르게 처리하는 것은 당연합니다. 설계의 질은 예상치 못한 일이 발생했을 때 어느 쪽으로 기울도록 미리 결정해 두었느냐에서 결정된다고 생각합니다.
설계 판단 ③ 검사는 "저렴한 순서"대로 나열한다
요청 (Request)이 왔을 때, 처리 순서는 다음과 같이 구성합니다.
① 레이트 리밋 (Rate Limit, 카운터 확인만 수행)
② 인증 (Authentication, 열쇠 대조. 암호 계산 수행)
③ 서버 구성 (가장 무거운 작업)
반대로 하면 어떤 일이 벌어질까요? 인증을 먼저 수행하는 상태에서 잘못된 열쇠로 10만 번 호출되었다고 가정해 봅시다. 10만 번 분량의 암호 계산을 모두 수행한 뒤에야, 10만 번 모두 거절하게 됩니다.
거절하는 작업 그 자체로 무너집니다. 보호하기 위해 도입한 인증이 그대로 공격의 도구가 됩니다.
Rate Limit (속도 제한)을 먼저 설정하면, 61회째부터는 카운터만 확인하고 거절할 수 있습니다. 암호 계산 단계까지 도달하지 않습니다.
올바른 처리를 구현했더라도, 순서를 잘못 정하면 역효과를 낳습니다. 이는 인증에만 국한된 이야기는 아니라고 생각합니다.
약점과 한계
지금까지 기술해 온 설계에도 성립 조건과 타협한 부분이 있습니다. 숨기지 않고 적어두겠습니다.
1. 역할을 헤더(Header)로 받고 있음
이 서버는 X-User-Role이라는 헤더 값을 신뢰합니다. 즉——올바른 토큰을 가진 상대라면, 헤더를 바꿔 쓰는 것만으로 admin이 될 수 있습니다.
이는 의도된 동작입니다. 이 설계는 신뢰할 수 있는 호출자 (Bot 또는 Gateway) 뒤에 배치한다는 전제를 가지고 있기 때문입니다. 본인 확인은 그 앞 단계에서 완료되며, 이 서버는 "누구로 취급할 것인가"만을 전달받습니다.
역할 판정을 각 서비스에서 이중으로 구현하면, 판정 결과가 어긋났을 때 알아차릴 수 없습니다. 판정은 한 곳으로 모으고, 후단에서는 그 결과를 받기만 하는 방식으로 역할을 분담했습니다.
따라서 이 서버를 인터넷에 직접 노출하면 그대로 취약점이 됩니다. 그럴 경우에는 역할을 헤더가 아닌 토큰 자체 (서명된 토큰 등)에서 도출하도록 수정하십시오.
2. Rate Limit (속도 제한)이 프로세스의 메모리에만 있음
Serverless (서버리스)나 다중 인스턴스로 구동할 경우, 실행할 때마다 서로 다른 메모리를 사용할 수 있으므로 전체적으로는 상한선을 초과하여 통과할 수 있습니다. 엄격하게 적용하고 싶다면 Redis 등의 외부 저장소로 교체하십시오. 교체해야 할 위치는 파일 하나로 제한되어 있습니다.
3. 고정 윈도우 (Fixed Window) 방식이므로, 경계에서 최대 2배 통과 가능
59초에 60회, 61초에 60회가 발생하면 2초 동안 120회가 통과됩니다. 엄격하게 관리하려면 슬라이딩 윈도우 (Sliding Window) 방식으로 변경해야 합니다.
템플릿의 목적은 "인증과 역할 제어 (Role Control)의 형식을 보여주는 것"이므로, 이 부분은 의도적으로 최소한으로 구현했습니다.
요약
MCP 서버를 만드는 것 자체는 그리 어렵지 않습니다. 공식 샘플대로 하면 작동합니다.
어려운 것은 그것을 사내에서 승인할 수 있는 상태로 만드는 것이었습니다.
이 글에서 쓴 세 가지는 결국 모두 같은 이야기를 하고 있습니다.
쓰는 것을 잊었을 때 → 그 도구는 아무에게도 보이지 않게 된다 -
모르는 값이 왔을 때 → 가장 낮은 권한이 된다 -
대량으로 호출되었을 때 → 가장 저렴한 검사로 먼저 거절한다
예상치 못한 일이 발생했을 때, 어느 쪽으로 기울 것인가. 결정해 두었는지 여부, 그것뿐입니다.
특히 두 번째. 모르는 값이 왔을 때 admin으로 만들지 않는 것. 한 줄이면 끝날 이야기지만, 반대로 하면 헤더를 보내지 않는 것만으로 관리자가 될 수 있는 서버가 만들어집니다. 정상적으로 작동해 버리기 때문에 아무도 눈치채지 못합니다.
코드는 MIT 라이선스로 공개하고 있습니다. 그대로 사용하셔도 무방합니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기