NGINX.COM
Web Server Load Balancing with NGINX Plus

NGINX 一直被视为打造高性能 Web 服务器和负载均衡器的首选之一。不仅如此,随着微服务架构的兴起以及对高效 API 管理的需求日益增长,NGINX 还成为了构建 API 网关的热门选择。

本文将介绍如何使用声明式 API 方法,将 OpenAPI 模式定义转换为全功能 NGINX 配置,从而创建具有 Web 应用防火墙安全防护功能和开发人员门户的 API 网关运行),并将分步说明如何利用 NGINX Plus 简化 API 管理流程,确保最佳应用性能。

 

API 网关简介

API 网关是管理和保护客户端与后端服务之间通信的中心枢纽,充当客户端与后端服务器之间的反向代理,可对传入请求进行路由,并将其分发到相应的服务。这不仅能够提高客户端与服务之间的通信效率,而且还支持 API 网关处理身份验证、授权、速率限制及缓存等任务。

能够执行加密、基于令牌的身份验证及访问控制等安全防护措施,确保只有授权用户才可访问服务。API 网关支持一站式整合和管理这些安全功能,有助于简化系统的整体安全架构,并降低跨多个服务实施安全防护措施的复杂性。

 

NGINX 声明式 API 项目

社区支持的 NGINX 声明式 API 项目为 NGINX Instance Manager 提供了一组声明式 REST API。

它可用于管理 NGINX Plus 配置生命周期,并使用 JSON 服务定义创建 NGINX Plus 配置。当与 NGINX Instance Manager 结合使用时,该项目支持 GitOps 集成:检查信源以获得引用对象更新并保持 NGINX 配置自动同步。

OpenAPI 模式可用于将 NGINX 自动配置为 API 网关。通过 Redocly 支持开发人员门户创建。

 

准备工作

要运行本文所述内容,您需要:

  • 运行中的 NGINX Instance Manager 实例
  • 订阅 NGINX PlusNGINX App Protect WAF(或获取 30 天免费试用版)。必须安装 NGINX Agent,以便连接到 NGINX Instance Manager,并加入 declarativeAPITest 实例组
  • 一台装有 docker 和 docker-compose 的 Linux 主机(裸机或虚拟机),用于运行 NGINX 声明式 API 项目
  • Postman,用于提交声明式 API 请求
  • 一台客户端主机,用于运行 Postman

 

实验概述

在安装并运行所有必备组件后,NGINX Instance Manager 显示 NGINX Plus 实例与 NGINX App Protect WAF 联机。

NGINX Plus 实例是 declarativeAPITest 实例组的一部分。


 

部署声明式 API

NGINX 声明式 API 项目依赖于 NGINX Instance Manager 提供的 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 into 'NGINX-Declarative-API'...
remote: Enumerating objects: 4072, done.
remote: Counting objects: 100% (1982/1982), done.
remote: Compressing objects: 100% (1332/1332), done.
remote: Total 4072 (delta 668), reused 876 (delta 609), pack-reused 2090
Receiving objects: 100% (4072/4072), 19.05 MiB | 4.88 MiB/s, done.
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 启动所有容器。在初始启动过程中,所有 docker 镜像都会自动构建:


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.检查正在运行的 docker 容器:


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 并导入 NGINX 声明式 API 集合(网址为:https://raw.githubusercontent.com/f5devcentral/NGINX-Declarative-API/main/contrib/postman/NGINX%20Declarative%20API.postman_collection.json

7.编辑 Postman 集合变量,根据环境进行调整:

8.设置以下变量:

  • ncg_host – 运行声明式 API docker-compose 的 Linux 主机的主机名或 IP 地址
  • ncg_port – NGINX 声明式 API 的 TCP 端口:默认为 5000
  • nim_host – NGINX Instance Manager 基本 URL(即 https://nms.k8s.ie.ff.lan)
  • nim_username – NGINX Instance Manager 身份验证用户名
  • nim_password – NGINX Instance Manager 身份验证密码

9.保存 Postman 上的所有变更

10.在 Postman 集合中,浏览到 Petstore API Gateway 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
                            }
                        ]
                    }
                }
            ]
        }
    }
}

“output” 部分定义:

  • 声明式 API 将 NGINX 配置发布到的目标 NGINX Instance Manager 服务器
  • TLS 证书和密钥 —— 可通过存储这些证书和密钥的信源 URL 进行引用
  • NGINX App Protect WAF 安全策略 —— 可通过存储这些策略的信源 URL 进行引用

“declaration” 部分描述:

  • 要创建的 NGINX 服务器
  • 是否执行 TLS 卸载
  • 记录访问和错误条目的位置
  • “/petstore” 基本 URI,此处将部署 API 网关配置并可供客户端访问
  • 要发布的 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.使用 Postman Send 按钮将请求发布到声明式 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>
    

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 App Protect 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
<html><head><title>Request Rejected</title></head><body>The requested URL was rejected. Please consult with your administrator.<br><br>Your support ID is: 7283327928460093545<br><br><a href='javascript:history.back();'>[Go Back]</a></body></html>

7.浏览以下网址即可访问开发人员门户


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


 

立即试用

如欲试用本文中介绍的 NGINX 解决方案,请立即下载 30 天免费试用版,或者联系我们讨论您的用例:

您还可以下载免费的开源软件 NGINX Agent

Hero image
免费白皮书:
NGINX 企阅版全解析

助力企业用户规避开源治理风险,应对开源使用挑战

关于作者

Fabrizio Fiorucci

欧洲、中东和非洲地区解决方案架构师

关于 F5 NGINX

F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 nginx-cn.net 了解更多相关信息。