이 글은 NGINX Open Source 및 NGINX Plus를 API 게이트웨이로 배포하는 방법을 다루는 블로그 시리즈의 두 번째 게시물입니다.
- 1부에서는 여러 사용 사례에 대한 자세한 구성 지침을 제공합니다.
- 2018년에 처음 게시된 이 글은 현재의 API 구성 모범 사례를 반영하도록 업데이트되었으며, 중첩된
location블록을 사용해 재작성 규칙 대신 요청을 라우팅합니다.
이 게시물에서는 이러한 사용 사례를 확장하여 프로덕션 환경의 백엔드 API 서비스를 보호하고 보안을 강화하는 데 적용할 수 있는 다양한 안전장치를 살펴봅니다:
- 3부에서는 NGINX Open Source와 NGINX Plus를 gRPC 서비스용 API 게이트웨이로 배포하는 방법을 설명합니다.
참고: 별도로 명시된 경우를 제외하면 이 게시물의 모든 내용은 NGINX Open Source와 NGINX Plus에 모두 적용됩니다. 읽기 쉽도록 이 블로그의 나머지 부분에서는 두 제품을 통칭해 간단히 “NGINX”라고 합니다.
요청 속도 제한
브라우저 기반 클라이언트와 달리, 개별 API 클라이언트는 API에 막대한 부하를 줄 수 있으며, 시스템 리소스의 상당 부분을 소모해 다른 API 클라이언트가 사실상 이용할 수 없게 만들 수도 있습니다. 이러한 위협은 악의적인 클라이언트에만 국한되지 않습니다. 오작동하거나 버그가 있는 API 클라이언트가 반복 루프에 빠져 백엔드에 과부하를 일으킬 수도 있습니다. 이를 방지하기 위해 각 클라이언트의 공정한 사용을 보장하고 백엔드 서비스의 리소스를 보호하도록 요청 속도 제한을 적용합니다.
NGINX는 요청의 모든 속성을 기준으로 요청 속도 제한을 적용할 수 있습니다. 일반적으로 클라이언트 IP 주소를 사용하지만, API에 인증이 활성화된 경우에는 인증된 클라이언트 ID가 더 신뢰할 수 있고 정확한 속성입니다.
속도 제한 자체는 최상위 API 게이트웨이 구성 파일에 정의되며, 이후 전역으로 적용하거나 API별 또는 URI별로 적용할 수 있습니다.
코드 예제를 로드하는 중…
이 예에서 limit_req_zone 지시문은 4번째 줄에서 각 클라이언트 IP 주소($binary_remote_addr)에 대해 초당 10개의 요청으로 속도 제한을 정의하고, 5번째 줄에서는 인증된 각 클라이언트 ID($http_apikey)에 대해 초당 200개의 요청으로 제한을 정의합니다. 이는 여러 속도 제한을 적용 위치와 독립적으로 정의할 수 있음을 보여 줍니다. API는 여러 속도 제한을 동시에 적용하거나 리소스별로 서로 다른 속도 제한을 적용할 수 있습니다.
그런 다음 다음 구성 스니펫에서는 limit_req 지시어를 사용하여 1부<.htmla>에서 설명한 “Warehouse API”의 정책 섹션에 첫 번째 요청 속도 제한을 적용합니다. 기본적으로 NGINX는 요청 속도 제한을 초과하면 503 (Service Unavailable) 응답을 보냅니다. 하지만 API 클라이언트가 동작을 조정할 수 있도록 요청 속도 제한을 초과했다는 사실을 명확히 알려 주는 것이 좋습니다. 이를 위해 limit_req_status 지시어를 사용하여 대신 429 (Too Many Requests) 응답을 보냅니다.
코드 예제를 로드하는 중…
추가 매개변수를 limit_req 지시문에 사용하여 NGINX가 요청 속도 제한을 적용하는 방식을 세부적으로 조정할 수 있습니다. 예를 들어 제한을 초과했을 때 요청을 즉시 거부하는 대신 대기열에 추가하여 요청 속도가 정의된 제한 아래로 떨어질 때까지 기다리게 할 수 있습니다. 요청 속도 제한을 세부적으로 조정하는 방법에 대한 자세한 내용은 블로그의 NGINX 및 NGINX Plus를 사용한 요청 속도 제한을 참조하세요.
특정 요청 메서드 적용
RESTful API에서 HTTP 메서드(또는 동사)는 각 API 호출의 중요한 구성 요소이며 API 정의에서도 매우 큰 의미를 갖습니다. Warehouse API의 가격 책정 서비스를 예로 들어 보겠습니다:
GET/api/warehouse/pricing/item001은 item001의 가격을 반환합니다PATCH/api/warehouse/pricing/item001은 item001의 가격을 변경합니다
Warehouse API의 URI 라우팅 정의를 업데이트하여 가격 책정 서비스에 대한 요청에서는 이 두 가지 HTTP 메서드만 허용하고, 재고 서비스에 대한 요청에서는 GET 메서드만 허용할 수 있습니다.
코드 예제를 로드하는 중…
이 구성을 적용하면 22행에 나열된 메서드 이외의 메서드를 사용하는 가격 책정 서비스 요청(및 13행에 있는 메서드 이외의 메서드를 사용하는 인벤토리 서비스 요청)은 거부되며 백엔드 서비스로 전달되지 않습니다. NGINX는 다음 콘솔 추적에 표시된 것처럼 API 클라이언트에 오류의 정확한 특성을 알리기 위해 405 (Method Not Allowed) 응답을 보냅니다. 정보 공개를 최소화하는 보안 정책이 필요한 경우에는 error_page 지시문을 사용하여 이 응답을 보다 적은 정보를 제공하는 오류(예: 400 (Bad Request))로 대신 변환할 수 있습니다.
$ curl https://api.example.com/api/warehouse/pricing/item001{"sku":"item001","price":179.99}
$ curl -X DELETE https://api.example.com/api/warehouse/pricing/item001
{"status":405,"message":"Method not allowed"}세분화된 액세스 제어 적용
이 시리즈의 1부에서는 API 키 및 JSON 웹 토큰(JWT)과 같은 인증 옵션을 활성화하여 무단 액세스로부터 API를 보호하는 방법을 설명했습니다. 인증된 ID 또는 인증된 ID의 속성을 사용하여 세분화된 액세스 제어를 수행할 수 있습니다.
여기에서는 이러한 예시 두 가지를 살펴보겠습니다.
- The 첫 번째 예에서는 특정 API 리소스에 대한 액세스를 제어합니다. 이를 위해 1부에서 소개한 구성을 확장하고 API 키 인증을 사용하여 해당 API 클라이언트가 허용 목록에 있는지 확인합니다.
- The 두 번째 예에서는 클라이언트가 사용할 수 있는 HTTP 메서드를 제어합니다. 또한 1부에서 언급한 JWT 인증 방식을 구현하며, 사용자 지정 클레임을 사용해 자격을 갖춘 API 클라이언트를 식별합니다. (JWT 지원은 NGINX Plus에서만 제공됩니다.)
물론 이러한 사용 사례에는 HTTP 기본 인증 및 OAuth 2.0 토큰 검사와 같은 다른 인증 방법도 적용할 수 있습니다.
특정 리소스에 대한 액세스 제어
Warehouse API 인벤토리 서비스의 audit 리소스에는 “인프라 클라이언트”만 액세스할 수 있도록 허용한다고 가정해 보겠습니다. API 키 인증을 활성화한 상태에서 map 블록을 사용해 인프라 클라이언트 이름의 허용 목록을 생성하면, 해당 API 키가 사용될 때 변수 $is_infrastructure의 값이 1로 평가됩니다.
코드 예제를 로드하는 중…
Warehouse API 정의에서 인벤토리 location 리소스에 대한 audit 블록을 추가합니다(15~20행). if 블록은 인프라 클라이언트만 해당 리소스에 액세스할 수 있도록 합니다.
코드 예제를 로드하는 중…
15번째 줄의 location 지시문은 =(등호) 수정자를 사용하여 audit 리소스와 정확히 일치하도록 설정되어 있습니다. 정확한 일치는 다른 리소스에 사용되는 기본 경로 접두사 정의보다 우선합니다. 다음 추적 결과는 이 구성이 적용된 상태에서 허용 목록에 없는 클라이언트가 인벤토리 audit 리소스에 액세스할 수 없음을 보여 줍니다. 표시된 API 키는 client_two의 것입니다(1부에서 정의됨).
$ curl -H "apikey: QzVV6y1EmQFbbxOfRCwyJs35" https://api.example.com/api/warehouse/inventory/audit{"status":403,"message":"Forbidden"}특정 메서드에 대한 액세스 제어
As defined 위에서 정의한 대로, 가격 책정 서비스는 GET 및 PATCH 메서드를 허용하며, 클라이언트는 이를 통해 각각 특정 품목의 가격을 조회하고 수정할 수 있습니다. (가격 책정 데이터의 전체 수명 주기를 관리할 수 있도록 POST 및 DELETE 메서드도 허용할 수 있습니다.) 이 섹션에서는 이 사용 사례를 확장하여 특정 사용자가 실행할 수 있는 메서드를 제어합니다. Warehouse API에 JWT 인증을 사용하도록 설정하면 각 클라이언트의 권한이 사용자 지정 클레임으로 인코딩됩니다. 가격 책정 데이터를 변경할 권한이 있는 관리자에게 발급되는 JWT에는 "admin":true 클레임이 포함됩니다. 이제 관리자만 변경할 수 있도록 액세스 제어 로직을 확장합니다.
코드 예제를 로드하는 중…
This map 블록은 api_gateway.conf 하단에 추가되며, 요청 메서드($request_method)를 입력으로 받아 새 변수인 $admin_permitted_method를 생성합니다. 읽기 전용 메서드는 항상 허용되지만(62~64행), 쓰기 작업에 대한 액세스 권한은 JWT의 admin 클레임 값에 따라 결정됩니다(65행). 이제 관리자만 가격을 변경할 수 있도록 Warehouse API 구성을 확장합니다.
코드 예제를 로드하는 중…
Warehouse API를 사용하는 모든 클라이언트는 유효한 JWT를 제시해야 합니다(7행). 또한 $admin_permitted_method 변수(25행)를 평가하여 쓰기 작업이 허용되는지 확인합니다. 다시 한번 말씀드리지만, JWT 인증은 NGINX Plus에서만 지원됩니다.
요청 크기 제어
HTTP API는 일반적으로 백엔드 API 서비스가 처리할 명령과 데이터를 요청 본문에 담습니다. 이는 XML/SOAP API뿐만 아니라 JSON/REST API에도 해당합니다. 따라서 요청 본문은 백엔드 API 서비스에 대한 공격 경로가 될 수 있으며, 이러한 서비스는 매우 큰 요청 본문을 처리할 때 버퍼 오버플로 공격에 취약할 수 있습니다.
기본적으로 NGINX는 본문 크기가 1 MB를 초과하는 요청을 거부합니다. 이미지 처리와 같이 대용량 페이로드를 특별히 다루는 API의 경우 이 제한을 늘릴 수 있지만, 대부분의 API에는 더 낮은 값을 설정합니다.
코드 예제를 로드하는 중…
7번째 줄의 client_max_body_size 지시어는 요청 본문의 크기를 제한합니다. 이 구성을 적용하면 가격 책정 서비스에 서로 다른 두 개의 PATCH 요청을 보냈을 때 API 게이트웨이가 어떻게 동작하는지 비교할 수 있습니다. 첫 번째 curl 명령은 소량의 JSON 데이터를 전송하는 반면, 두 번째 명령은 대용량 파일(/etc/services)의 내용을 전송하려고 시도합니다.
$ curl -iX PATCH -d '{"price":199.99}' https://api.example.com/api/warehouse/pricing/item001HTTP/1.1 204 No Content
Server: nginx/1.19.5
Connection: keep-alive
$ curl -iX PATCH -d@/etc/services https://api.example.com/api/warehouse/pricing/item001
HTTP/1.1 413 Request Entity Too Large
Server: nginx/1.19.5
Content-Type: application/json
Content-Length: 45
Connection: close
{"status":413,"message":"Payload too large"}요청 본문 유효성 검사
[편집자 – 다음 사용 사례는 NGINX JavaScript 모듈의 여러 사용 사례 중 하나입니다. 전체 목록은 NGINX JavaScript 모듈 사용 사례를 참조하세요.]
백엔드 API 서비스는 대용량 요청 본문을 이용한 버퍼 오버플로 공격에 취약할 뿐만 아니라, 유효하지 않거나 예상치 못한 데이터가 포함된 요청 본문에도 영향을 받을 수 있습니다. 요청 본문에 올바른 형식의 JSON이 필요한 애플리케이션에서는 NGINX JavaScript module<.htmla>을 사용하여 백엔드 API 서비스로 프록시하기 전에 JSON 데이터가 오류 없이 파싱되는지 확인할 수 있습니다.
With the JavaScript 모듈을 설치한 상태에서, JSON 데이터의 유효성을 검사하는 함수의 JavaScript 코드가 포함된 파일을 참조하기 위해 js_import 지시어를 사용합니다.
코드 예제를 로드하는 중…
The js_set 지시어는 새 변수 $json_validated을 정의하며, 이 변수는 parseRequestBody 함수를 호출하여 평가됩니다.
코드 예제를 로드하는 중…
The parseRequestBody 함수는 JSON.parse 메서드(6행)를 사용하여 요청 본문을 구문 분석하려고 시도합니다. 구문 분석에 성공하면 이 요청의 대상 업스트림 그룹 이름이 반환됩니다(8행). 요청 본문을 구문 분석할 수 없어 예외가 발생하면 로컬 서버 주소가 반환됩니다(11행). return 지시어는 요청을 어디로 보낼지 결정하는 데 사용할 수 있도록 $json_validated 변수를 설정합니다.
코드 예제를 로드하는 중…
Warehouse API의 URI 라우팅 섹션에서 22번째 줄의 proxy_pass 지시문을 수정합니다. 이 지시문은 이전 섹션에서 설명한 Warehouse API 구성과 마찬가지로 요청을 백엔드 API 서비스로 전달하지만, 이제 $json_validated 변수를 대상 주소로 사용합니다. 클라이언트 본문이 JSON으로 성공적으로 파싱되면 15번째 줄에 정의된 업스트림 그룹으로 프록시합니다. 반면 예외가 발생한 경우에는 반환된 값인 127.0.0.1:10415를 사용하여 클라이언트에 오류 응답을 보냅니다.
코드 예제를 로드하는 중…
요청이 이 가상 서버로 프록시되면 NGINX는 클라이언트에 415 (지원되지 않는 미디어 유형) 응답을 보냅니다.
이 전체 구성을 적용하면 NGINX는 요청에 올바른 형식의 JSON 본문이 포함된 경우에만 해당 요청을 백엔드 API 서비스로 프록시합니다.
$ curl -iX POST -d '{"sku":"item002","price":85.00}' https://api.example.com/api/warehouse/pricingHTTP/1.1 201 Created
Server: nginx/1.19.5
Location: /api/warehouse/pricing/item002
$ curl -X POST -d 'item002=85.00' https://api.example.com/api/warehouse/pricing
{"status":415,"message":"Unsupported media type"}A Note about the $request_body 변수에 대한 참고 사항
JavaScript 함수 parseRequestBody는 JSON 구문 분석을 수행하기 위해 $request_body 변수를 사용합니다. 그러나 NGINX는 기본적으로 이 변수를 채우지 않고, 중간 복사본을 만들지 않은 채 요청 본문을 백엔드로 바로 스트리밍합니다. URI 라우팅 섹션(16번째 줄)에서 mirror 지시어를 사용하면 클라이언트 요청의 복사본이 생성되고, 그 결과 $request_body 변수가 채워집니다.
코드 예제를 로드하는 중…
17행과 19행의 지시문은 NGINX가 내부적으로 요청 본문을 처리하는 방식을 제어합니다. 요청 본문이 디스크에 기록되지 않도록 client_body_buffer_size를 client_max_body_size와 동일한 크기로 설정합니다. 이렇게 하면 디스크 I/O 작업을 최소화하여 전반적인 성능이 향상되지만, 메모리 사용량이 늘어납니다. 요청 본문이 작은 대부분의 API 게이트웨이 사용 사례에서는 적절한 절충안입니다.
앞서 언급했듯이 mirror 지시어는 클라이언트 요청의 복사본을 생성합니다. $request_body를 채우는 것 외에는 이 복사본이 필요하지 않으므로, 최상위 API 게이트웨이 구성의 /_get_request_body 블록에 정의한 “막다른” 위치(server)로 전송합니다.
코드 예제를 로드하는 중…
이 location은 단지 204 (No Content) 응답을 전송할 뿐입니다. 이 응답은 미러링된 요청과 관련되어 있으므로 무시되며, 원래 클라이언트 요청을 처리할 때 발생하는 오버헤드는 미미합니다.
요약
NGINX Open Source와 NGINX Plus를 API 게이트웨이로 배포하는 방법을 다루는 시리즈의 두 번째 블로그 게시물에서는 프로덕션 환경에서 악의적이거나 비정상적으로 작동하는 클라이언트로부터 백엔드 API 서비스를 보호하는 과제에 중점을 두었습니다. NGINX는 API 트래픽을 관리하기 위해 현재 인터넷에서 트래픽이 가장 많은 사이트를 구동하고 보호하는 데 사용되는 것과 동일한 기술을 사용합니다.
이 시리즈의 다른 게시물도 확인해 보세요:
- 1부에서는 몇 가지 필수 API 게이트웨이 사용 사례에 맞게 NGINX를 구성하는 방법을 설명합니다.
- 3부에서는 NGINX를 gRPC 서비스용 API 게이트웨이로 배포하는 방법을 설명합니다.
NGINX Plus를 API 게이트웨이로 사용해 보려면 지금 30일 무료 평가판을 시작하거나 사용 사례에 대해 논의하려면 문의해 주세요. 평가 기간에는 GitHub Gist 저장소에서 전체 구성 파일 세트를 사용하세요.
작성자 소개

관련 블로그 게시물
시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.
F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.
Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.
진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.
NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.
Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.