Skip to content
isdnetworks
Go back

밖에서 오는 문과 안에서 쓰는 문

서비스에 붙는 경로가 하나였다. 밖에서 오는 사용자도 안에서 도는 배치도 같은 주소로 들어왔다.

Table of contents

Open Table of contents

제약 — 하나의 문이 만든 타협

들어오는 자리가 이랬다.

사용자    → api.example.com → 서버
배치      → api.example.com → 서버
관리 도구 → api.example.com → 서버

api.example.com 앞단에 방어 장치를 두면 안쪽 것까지 걸린다.

배치가 초당 수십 번 부르면 과도한 요청으로 차단된다. 예외로 빼면 그 예외 조건을 밖에서도 흉내 낼 수 있다.

User-Agent 로 배치를 가려내는 방법도 생각해 봤지만 그 값은 밖에서 얼마든지 지정할 수 있다. 요청 안에 든 것으로 안팎을 가르는 방식은 전부 같은 한계를 갖는다.

그래서 방어 설정을 느슨하게 두고 있었고 밖을 막으려면 안이 막히고 안을 열면 밖도 열리는 상태였다.

문이 하나면 그 문의 규칙도 하나라 서로 다른 요구를 담을 수 없다. 구성을 나누지 않고는 이 타협에서 못 벗어난다.

나누기로 했다

이름을 둘로 갈랐다.

api.example.com       밖에서 오는 것. 방어 장치를 거친다
internal.example.com  안에서만. 사설 대역에서만 닿는다

apiinternal 이 같은 애플리케이션을 보는데 들어오는 문만 다르다.

# 밖으로 나가는 쪽
server_name api.example.com;
# 여기에 요청 수 제한, 봇 차단, 국가 제한 등을 건다

# 안쪽
server_name internal.example.com;
allow 10.0.0.0/24;
deny all;

server_name 으로 갈라 밖에는 방어를 세게 걸고 안쪽은 allow 대역으로만 막았다.

변경 내용 — 무엇이 어느 문으로

나누고 나서 부르는 쪽을 하나씩 옮겼다.

사용자 화면      밖
모바일 앱        밖
배치             안
관리 도구        안
모니터링         안
협력사 연동      밖 (별도 경로)

협력사가 애매했는데 밖에서 오지만 아는 상대이기 때문이다.

partner.example.com   협력사 전용. 키 인증 + 요청 수 제한

partner.example.com 으로 밖에 두되 경로를 따로 뒀다.

애매한 것을 억지로 둘 중 하나에 넣으면 거기서 다시 타협이 생기므로 세 번째를 만드는 편이 쌌다.

협력사를 안쪽으로 넣으면 남의 대역을 allow 에 적어야 한다. 밖에 그냥 두면 사용자와 같은 제한에 걸려 연동이 자주 막힌다.

검증 — 이름으로만 가르면 남는 구멍

설정만 하고 끝내지 않고 밖에서 찔러 봤다.

$ curl -sI https://internal.example.com/health
403 Forbidden

internal.example.com 으로 붙으면 403 으로 막힌다.

$ curl -sI -H "Host: internal.example.com" https://<서버IP>/health
200 OK

서버 주소를 직접 치고 Host 헤더를 넣으니 200 이 왔다.

앞단이 server_name 으로만 가르고 있었기 때문인데 안 맞는 요청이 어디로 갈지를 정해 두지 않으면 첫 블록이 받는다.

# 기본 서버 블록에서 막는다
server {
    listen 443 default_server;
    return 444;
}

default_server 에서 444 로 끊고 서버 주소 자체도 방화벽에서 막았다.

주의 — 밖에 열려 있던 안쪽 기능

경로를 나누면서 무엇이 열려 있었는지도 봤다.

/admin/cache/clear
/admin/config/reload
/debug/info

/admin//debug/ 가 밖에서 닿는 상태였고 인증이 있긴 했지만 있어야 할 자리가 아니었다.

안쪽 문으로만 열리게 옮기고 밖에서는 그 경로 자체를 막았다.

location ~ ^/(admin|debug)/ { return 404; }

404 를 준 것은 그 경로가 있는지 없는지도 안 알려 주기 위해서였다.

한 문으로 다 들어올 때는 이것을 확인할 기준 자체가 없었고 나누면서 무엇이 어느 쪽인지 정하다 보니 저절로 드러났다.

/debug/info 는 서버 구성을 그대로 뱉는 경로였다. 인증이 걸려 있었다고 해도 밖에서 그 주소가 응답한다는 것 자체가 정보였다.

로그도 나뉘어 보기 편해졌다

apiinternal 이 갈리니 각각에 다른 제한을 걸 수 있었다.

요청 수 제한   IP당 초당 10회
본문 크기      1MB
연결 시간      10초

밖은 이렇게 좁게 잡고 internal 쪽은 넉넉하게 뒀다.

배치가 큰 본문을 보내는 일이 있어 크기를 늘렸고 오래 걸리는 조회가 있어 시간도 늘렸다. 하나였을 때는 둘 중 하나에 맞춰야 했던 것이 각각에 맞춰졌다.

access_log 도 나뉘었다.

/var/log/nginx/api.access.log
/var/log/nginx/internal.access.log

밖의 로그에서 이상한 요청을 찾기가 쉬워졌다. 전에는 배치 요청이 대부분이라 그 안에서 찾아야 했고 나누는 것의 이득이 방어 설정만이 아니라 관측에도 있었다.

api.access.log 는 하루 분량이 훨씬 줄어서 눈으로 훑는 것도 가능해졌다. 같은 자료인데 섞여 있느냐 나뉘어 있느냐가 볼 수 있는 것을 바꿨다.

정리


Share this post on:

Previous Post
전역 규칙이 하위에도 로드된다
Next Post
태그가 유일한 비용 손잡이다