선언적 API를 사용하는 API 게이트웨이로서 OpenAPI에서 NGINX로 전환하기

관리자
조회수 356


고성능 웹 서버와 로드 밸런서를 구동하기 위한 최고의 선택 중 하나로 오랫동안 인정받아 온 Nginx.


그러나 마이크로서비스 아키텍처가 부상하고 효율적인 API 관리의 필요성이 대두됨에 따라


Nginx는 API 게이트웨이 구축에도 널리 사용되고 있습니다.


이 블로그에서는 선언적 API 접근 방식을 사용하여 OpenAPI 스키마 정의

'웹 애플리케이션 방화벽' 보안 및 '개발자 포털'을 갖춘 API 게이트웨이로 실행되는 완전한 기능의

NGINX 구성으로 전환하는 방법에 대해 살펴봅니다.


NGINX Plus를 활용하여 API 관리 프로세스를 간소화하고

애플리케이션의 성능을 최적으로 유지하는 방법에 대한 단계별 지침과 통찰력을 제공합니다.



API 게이트웨이 소개
API 게이트웨이는 클라이언트와 백엔드 서비스 간의 통신을 관리하고 보호하기 위한 중앙 허브 역할을 합니다. 클라이언트와 백엔드 서버 사이에 위치하여 들어오는 요청을 라우팅하고 적절한 서비스로 배포하는 역방향 프록시 역할을 합니다. 이를 통해 클라이언트와 서비스 간의 보다 효율적인 통신이 가능할 뿐만 아니라 API 게이트웨이가 인증, 권한 부여, 속도 제한, 캐싱과 같은 작업을 처리할 수 있습니다.

또한 API 게이트웨이는 잠재적인 위협과 공격으로부터 백엔드 서비스를 보호하는 '보안 계층' 역할을 할 수 있습니다. 암호화, 토큰 기반 인증, 접근 제어와 같은 보안 조치를 시행하여 권한이 부여된 사용자만 서비스에 접근할 수 있도록 할 수 있습니다. API 게이트웨이는 이러한 보안 기능을 한 곳에서 통합하고 관리함으로써 시스템의 전반적인 보안 아키텍처를 간소화하고 여러 서비스에서 보안 조치를 구현하는 복잡성을 줄이는 데 도움을 줍니다.

또한 API 게이트웨이는 잠재적인 위협과 공격으로부터 백엔드 서비스를 보호하는 '보안 계층' 역할을 할 수 있습니다. 암호화, 토큰 기반 인증, 접근 제어와 같은 보안 조치를 시행하여 권한이 부여된 사용자만 서비스에 접근할 수 있도록 할 수 있습니다. API 게이트웨이는 이러한 보안 기능을 한 곳에서 통합하고 관리함으로써 시스템의 전반적인 보안 아키텍처를 간소화하고 여러 서비스에서 보안 조치를 구현하는 복잡성을 줄이는 데 도움을 줍니다.


nginx 선언적 API 프로젝트

커뮤니티에서 지원하는 nginx 선언적 API 프로젝트는 nginx 인스턴스 매니저를 위한 선언적 rest API 집합을 제공합니다.

이 API는 NGINX Plus 구성 라이프사이클을 관리하고 JSON 서비스 정의를 사용하여

NGINX Plus 구성을 만드는 데 사용할 수 있습니다.


NGINX 인스턴스 관리자와 함께 사용할 경우 GitOps 통합이 지원되므로 다음과 같은 이점이 있습니다.

참조된 개체 업데이트에 대한 소스가 확인되고 NGINX 구성이 자동으로 동기화 상태로 유지됩니다.

OpenAPI 스키마를 사용하여 NGINX를 API 게이트웨이로 자동 구성할 수 있습니다. 개발자 포털 생성은 Redocly를 통해 지원됩니다.


전제 조건

이 블로그의 콘텐츠를 실행하려면 다음이 필요합니다:

  • 실행 중인 NGINX 인스턴스 매니저 인스턴스
  • NGINX Plus 및 NGINX App Protect WAF 구독(또는 30일 무료 체험판). 
  • NGINX 인스턴스 매니저에 연결하고 declarativeAPITest 인스턴스 그룹에 속하려면 NGINX Agent가 설치되어 있어야 합니다.
  • NGINX Declarative API 프로젝트를 실행하기 위한 docker 및 docker-compose가 있는 Linux 호스트(베어메탈 또는 VM)
  • 선언적 API 요청을 제출할 Postman
  • Postman을 실행할 클라이언트 호스트

실습 개요

모든 필수 구성 요소를 설치하고 실행한 후.

NGINX 인스턴스 관리자는 NGINX 앱 프로텍트 WAF를 사용하여 NGINX 플러스 인스턴스를 온라인 상태로 표시합니다.


NGINX Plus 인스턴스는 선언적APITest 인스턴스 그룹의 일부입니다.


선언적 API 배포

NGINX 선언적 API 프로젝트는 NGINX 인스턴스 매니저에서 제공하는 REST API에 의존하며 

선언적 JSON 기반 추상화를 제공합니다. 선언적 API 프로젝트를 실행하려면 다음 지침을 따르세요:

1. docker ps를 실행하여 Docker가 실행 중인지 확인합니다:

f5@ubuntu:~$ docker ps CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

2. Linux 호스트에서 Github 리포지토리를 복제합니다:

f5@ubuntu:~$ git clone https://github.com/f5devcentral/NGINX-Declarative-API/ Cloning with 'NGINX-Declarative-API'... remote: enumerating objects: 4072, done: counting objects: 100% (1982/1982), done: compressing objects: 100% (1332/1332), Complete: Total 4072 (delta 668), reuse 876 (delta 609), pack reuse 2090 Objects receiving: 100% (4072/4072), 19.05 MiB | 4.88 MiB/s, Completed. Resolving deltas: 100% (1154/1154), done. f5@ubuntu:~$

3. docker-compose 디렉토리로 변경합니다:

f5@ubuntu:~$ cd NGINX-Declarative-API/contrib/docker-compose/

4. nginx-dapi.sh 스크립트를 사용하여 docker-compose를 통해 모든 컨테이너를 시작합니다. 

초기 시작 시 모든 도커 이미지는 자동으로 빌드됩니다:


f5@ubuntu:~/NGINX-Declarative-API/contrib/docker-compose$ ./nginx-dapi.sh -c start
-> Updating docker images
[+] Pulling 11/11
[...]
-> Deploying NGINX Declarative API
[+] Running 4/4
 ✔ Network nginx-dapi_dapi-network  Created 0.1s 
 ✔ Container redis                  Started 1.5s 
 ✔ Container devportal              Started 1.5s 
 ✔ Container nginx-dapi             Started

5. 실행 중인 도커 컨테이너를 확인합니다:


f5@ubuntu:~/NGINX-Declarative-API/contrib/docker-compose$ docker ps
CONTAINER ID   IMAGE                             COMMAND                  CREATED         STATUS         PORTS                                       NAMES
e29a2f783da2   nginx-declarative-api             "/deployment/env/bin…"   5 minutes ago   Up 5 minutes   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   nginx-dapi
97142840eaf7   redis                             "docker-entrypoint.s…"   5 minutes ago   Up 5 minutes   0.0.0.0:6379->6379/tcp, :::6379->6379/tcp   redis
6b50c0426643   nginx-declarative-api-devportal   "/deployment/src/sta…"   5 minutes ago   Up 5 minutes   0.0.0.0:5001->5000/tcp, :::5001->5000/tcp   devportal


6. 클라이언트 호스트에서 Postman을 실행하고 

https://raw.githubusercontent.com/f5devcentral/NGINX-Declarative-API/main/contrib/postman/NGINX%20Declarative%20API.postman_collection.json 에서 NGINX 선언적 API 컬렉션을 가져옵니다.

7. Postman 컬렉션 변수를 편집하여 사용자 환경에 맞게 조정합니다:


8. 다음 변수를 설정합니다:

  • ncg_host - 선언적 API docker-compose가 실행 중인 Linux 호스트의 호스트 이름 또는 IP 주소입니다.
  • ncg_port - NGINX Declarative API의 TCP 포트: 기본값은 5000입니다.
  • nim_host - NGINX 인스턴스 매니저 기본 URL(예: https://nms.k8s.ie.ff.lan)
  • nim_username - NGINX 인스턴스 관리자 인증 사용자 이름
  • nim_password - NGINX 인스턴스 관리자 인증 비밀번호


9. 모든 변경 사항을 포스트맨에 저장하기

10. Postman 컬렉션에서 펫스토어 API 게이트웨이 RateLimit + JWT AuthN/AuthZ + WAF로 이동하여 요청을 엽니다.


JSON 선언은 다음과 같습니다:

{
    "output": {
        "type": "nms",
        "nms": {
            "url": "{{nim_host}}",
            "username": "{{nim_username}}",
            "password": "{{nim_password}}",
            "instancegroup": "{{nim_instancegroup}}",
            "synctime": 0,
            "modules": [
                "ngx_http_app_protect_module"
            ],
            "certificates": [
                {
                    "type": "certificate",
                    "name": "test_cert",
                    "contents": {
                        "content": "{{github_gitops_root}}/v4.2/testcert.crt"
                    }
                },
                {
                    "type": "key",
                    "name": "test_key",
                    "contents": {
                        "content": "{{github_gitops_root}}/v4.2/testcert.key"
                    }
                }
            ],
            "policies": [
                {
                    "type": "app_protect",
                    "name": "production-policy",
                    "active_tag": "xss-blocked",
                    "versions": [
                        {
                            "tag": "xss-blocked",
                            "displayName": "Production Policy - XSS blocked",
                            "description": "This is a production-ready policy - XSS blocked",
                            "contents": {
                                "content": "{{github_gitops_root}}/v4.2/nap-policy-xss-blocked-bot-allowed.json"
                            }
                        },
                        {
                            "tag": "xss-allowed",
                            "displayName": "Production Policy - XSS allowed",
                            "description": "This is a production-ready policy - XSS allowed",
                            "contents": {
                                "content": "{{github_gitops_root}}/v4.2/nap-policy-xss-allowed.json"
                            }
                        }
                    ]
                }
            ]
        }
    },
    "declaration": {
        "http": {
            "servers": [
                {
                    "name": "Petstore API",
                    "names": [
                        "apigw.nginx.lab"
                    ],
                    "resolver": "8.8.8.8",
                    "listen": {
                        "address": "0.0.0.0:443",
                        "http2": true,
                        "tls": {
                            "certificate": "test_cert",
                            "key": "test_key",
                            "ciphers": "DEFAULT",
                            "protocols": [
                                "TLSv1.2",
                                "TLSv1.3"
                            ]
                        }
                    },
                    "log": {
                        "access": "/var/log/nginx/apigw.nginx.lab-access_log",
                        "error": "/var/log/nginx/apigw.nginx.lab-error_log"
                    },
                    "locations": [
                        {
                            "uri": "/petstore",
                            "urimatch": "prefix",
                            "apigateway": {
                                "openapi_schema": {
                                    "content": "http://petstore.swagger.io/v2/swagger.json"
                                },
                                "api_gateway": {
                                    "enabled": true,
                                    "strip_uri": true,
                                    "server_url": "https://petstore.swagger.io/v2"
                                },
                                "developer_portal": {
                                    "enabled": true,
                                    "uri": "/petstore-devportal.html"
                                },
                                "authentication": {
                                    "client": [
                                        {
                                            "profile": "Petstore JWT Authentication"
                                        }
                                    ],
                                    "enforceOnPaths": true,
                                    "paths": [
                                        "/user/login",
                                        "/user/logout"
                                    ]
                                },
                                "authorization": [
                                    {
                                        "profile": "JWT role based authorization",
                                        "enforceOnPaths": true,
                                        "paths": [
                                            "/user/login",
                                            "/user/logout"
                                        ]
                                    }
                                ],
                                "rate_limit": [
                                    {
                                        "profile": "petstore_ratelimit",
                                        "httpcode": 429,
                                        "burst": 0,
                                        "delay": 0,
                                        "enforceOnPaths": true,
                                        "paths": [
                                            "/user/login",
                                            "/user/logout"
                                        ]
                                    }
                                ]
                            },
                            "log": {
                                "access": "/var/log/nginx/petstore-access_log",
                                "error": "/var/log/nginx/petstore-error_log"
                            },
                            "app_protect": {
                                "enabled": true,
                                "policy": "production-policy",
                                "log": {
                                    "profile_name": "secops_dashboard",
                                    "enabled": true,
                                    "destination": "127.0.0.1:514"
                                }
                            }
                        }
                    ]
                }
            ],
            "rate_limit": [
                {
                    "name": "petstore_ratelimit",
                    "key": "$binary_remote_addr",
                    "size": "10m",
                    "rate": "2r/s"
                }
            ],
            "authentication": {
                "client": [
                    {
                        "name": "Petstore JWT Authentication",
                        "type": "jwt",
                        "jwt": {
                            "realm": "Petstore Authentication",
                            "key": "{\"keys\": [{\"k\":\"ZmFudGFzdGljand0\",\"kty\":\"oct\",\"kid\":\"0001\"}]}",
                            "cachetime": 5
                        }
                    }
                ]
            },
            "authorization": [
                {
                    "name": "JWT role based authorization",
                    "type": "jwt",
                    "jwt": {
                        "claims": [
                            {
                                "name": "roles",
                                "value": [
                                    "~(devops)"
                                ],
                                "errorcode": 403
                            }
                        ]
                    }
                }
            ]
        }
    }
}



출력 섹션은 다음을 정의합니다:

  • 선언적 API가 NGINX 구성을 게시할 NGINX 인스턴스 관리자 서버는 다음과 같습니다.
  • TLS 인증서 및 키 - 저장된 신뢰 소스의 URL로 참조할 수 있습니다.
  • NGINX 앱 보호 WAF 보안 정책 - 저장된 신뢰 소스의 URL로 참조할 수 있습니다.

선언 섹션에서 설명합니다:

  • 생성할 NGINX 서버
  • TLS 오프로드 수행 여부
  • 액세스 및 오류 항목을 기록할 위치
  • API 게이트웨이 구성이 배포되고 클라이언트가 액세스할 수 있는 /petstore 기본 URI입니다.
  • 게시할 API 게이트웨이 구성 및 개발자 포털
  • NGINX App Protect WAF 활성화 방법, 사용할 보안 정책 및 보안 위반을 기록할 위치
  • 클라이언트 요청을 인증하고 권한을 부여하는 방법


API 게이트웨이 선언 섹션에서는 선언적 API가 결과를 전달하는 방법을 설명합니다.

OpenAPI 스키마는 전체 URL을 통해 참조됩니다:


"apigateway": { "openapi_schema": { "content": "http://petstore.swagger.io/v2/swagger.json" },

NGINX API 게이트웨이 구성 생성이 요청되고 업스트림 서버가 정의됩니다. NGINX가 업스트림으로 요청을 역방향 프록시하면 /petstore 기본 URI가 제거됩니다:

"api_gateway": { "enabled": true, "strip_uri": true, "server_url": "https://petstore.swagger.io/v2" },

특정 URI로 개발자 포털 생성 및 배포가 요청됩니다:

"developer_portal": { "enabled": true, "uri": "/petstore-devportal.html" },

지정된 클라이언트 인증 프로필에 기반한 클라이언트 인증이 /user/login 및 /user/logout에 적용됩니다:

"authentication": {
        "client": [
            {
                "profile": "Petstore JWT Authentication"
            }
        ],
        "enforceOnPaths": true,
        "paths": [
            "/user/login",
            "/user/logout"
        ]
    },

지정된 클라이언트 권한 부여 프로필에 기반한 클라이언트 권한 부여가 /user/login 및 /user/logout에 적용됩니다:


"authorization": [
        {
            "profile": "JWT role based authorization",
            "enforceOnPaths": true,
            "paths": [
                "/user/login",
                "/user/logout"
            ]
        }
    ],
 

지정된 프로필에 따라 /user/login 및 /user/logout에 속도 제한이 적용됩니다:

"rate_limit": [
        {
            "profile": "petstore_ratelimit",
            "httpcode": 429,
            "burst": 0,
            "delay": 0,
            "enforceOnPaths": true,
            "paths": [
                "/user/login",
                "/user/logout"
            ]
        }
    ]
},



12. 포스트맨 보내기 버튼을 사용하여 선언적 API에 요청을 게시합니다. 응답은 다음과 유사합니다:


{
    "code": 200,
    "content": {
        "createTime": "2024-04-26T17:09:10.419574328Z",
        "details": {
            "failure": [],
            "pending": [],
            "success": [
                {
                    "name": "vm-test"
                }
            ]
        },
        "id": "1060ec49-120e-45ca-820b-5203c8b3538d",
        "message": "Instance Group config successfully published to declarativeAPITest",
        "status": "successful",
        "updateTime": "2024-04-26T17:09:10.881509913Z"
    },
    "configUid": "eecf1da6-9d8f-4e44-89cc-a470af79379d"
}

13. 이 단계에서 NGINX 인스턴스는 API 게이트웨이로 구성되고, WAF 보안이 적용되며, 개발자 포털이 게시됩니다.



API 게이트웨이 테스트

참고: FQDN apigw.nginx.lab은 NGINX 인스턴스가 실행 중인 가상 머신의 IP 주소로 확인한다고 가정합니다.

1. jwt 디렉토리로 변경합니다:


f5@ubuntu:~$ cd ~/NGINX-Declarative-API/contrib/gitops-examples/jwt


2. 인증되지 않은 REST API 엔드포인트에 액세스합니다:

$ curl -w '\n' -ki https://apigw.nginx.lab/petstore/store/inventory
HTTP/2 200 
date: Fri, 26 Apr 2024 17:13:54 GMT
content-type: application/json
access-control-allow-origin: *
access-control-allow-methods: GET, POST, DELETE, PUT
access-control-allow-headers: Content-Type, api_key, Authorization

{"totvs":5,"aut":1,"FORsold":1,[...]

3. 속도 제한: 


 $ curl -w '\n' -ki https://apigw.nginx.lab/petstore/user/login;curl -w '\n' -ki     
https://apigw.nginx.lab/petstore/user/login    HTTP/2 401     
date: Fri, 26 Apr 2024 17:14:51 GMT
    content-type: text/html    
content-length: 179
    www-authenticate: Bearer realm="Petstore Authentication"   
 <html>
    <head>
<title>401 Authorization Required</title>
</head>    
<body>
    <center>
<h1>401 Authorization Required</h1>
</center>    
<hr><center>nginx/1.25.3</center>
    </body>    </html>
    HTTP/2 429
     date: Fri, 26 Apr 2024 17:14:51 GMT 
   content-type: text/html
    content-length: 169
    <html>
    <head><title>429 Too Many Requests</title></head>
    <body>
    <center><h1>429 Too Many Requests</h1></center>
    <hr><center>nginx/1.25.3</center>    </body>
    </html>
$ curl -w '\n' -ki https://apigw.nginx.lab/petstore/user/login;curl -w '\n' -ki
https://apigw.nginx.lab/petstore/user/login HTTP/2 401 date: Fri, 26 Apr 2024 17:14:51 GMT     content-type: text/html    content-length: 179     www-authenticate: Bearer realm="Petstore Authentication"    <html>     <head><title>401 Authorization Required</title></head>     <body>    <center><h1>401 Authorization Required</h1></center>     <hr><center>nginx/1.25.3</center>    </body>    </html>    HTTP/2 429      date: Fri, 26 Apr 2024 17:14:51 GMT     content-type: text/html    content-length: 169     <html>    <head><title>429 Too Many Requests</title></head>     <body>    <center><h1>429 Too Many Requests</h1></center>     <hr><center>nginx/1.25.3</center>    </body>    </html>

4. 인증 및 유효한 권한 부여:

$ curl -w '\n' -ki https://apigw.nginx.lab/petstore/user/login -H "Authorization: Bearer `cat jwt.devops`"
 HTTP/2 200 date: Fri, 26 Apr 2024 17:15:41 GMT content-type: application/json
 access-control-allow-origin:*
 access-control-allow-methods: GET, POST, DELETE, PUT
 access-control-allow-headers: Content-Type, api_key,Authorization
 x-expires-after: Fri Apr 26 18:15:41 UTC 2024 x-rate-limit: 5000
{"code":200,"type":"unknown","message":"logged in user session:1714151741883"}

5. 인증 및 유효하지 않은 권한 부여:


$ curl -w '\n' -ki https://apigw.nginx.lab/petstore/user/login -H "Authorization: 
Bearer `cat jwt.guest`" HTTP/2 403 date: Fri, 26 Apr 2024 17:16:07 GMT content-type: text/html
 content-length: 153 <html> <head><title>403 Forbidden</title></head> 
<body> <center><h1>403 Forbidden</h1></center> 
<hr><center>nginx/1.25.3</center>
 </body> 
</html>


6. NGINX 앱 프로텍트 WAF 및 크로스 사이트 스크립팅 보안 위반:

$ curl -w '\n' -ki "https://apigw.nginx.lab/petstore/store/inventory?
"
HTTP/2 200 
content-type: text/html; charset=utf-8
cache-control: no-cache
pragma: no-cache
content-length: 246
Request RejectedThe requested URL was rejected. Please consult with your administrator.
Your support ID is: 7283327928460093545
<br><br><a href='history.back();'>[Go Back]</a></body></html>


7. 개발자 포털은 다음 주소로 접속하여 액세스할 수 있습니다.

https://apigw.nginx.lab/petstore/petstore-devportal.html


위 내용과 같이 NGINX Plus를 활용하여 Demo 가 필요하시면 하단의 전문가에게 상담받기 버튼을 클릭해주세요