开发一个 kong 网关插件禁止通过 ip 访问服务器
背景
服务统一使用了 kong 网关进行管理,并且不希望客户端通过服务器 ip 进行访问,必须要通过域名。
这个功需求点如果是在 nginx 处理起来会很简单,修改下配置文件里的 server_name
就行了,但是 kong 如何处理我没有在网上找到一个很好的解决方案,那只能自己写插件来处理了,也顺便借此学习下 kong 插件的开发,毕竟用 kong 哪有不写自定义插件的时候呢。
Kong 运行环境搭建
使用 docker 快速搭建 db-less 环境:
-
创建
docker-compose.yml
内容如下:version: "3.8" services: kong: image: kong:2.5.1 volumes: - ./kong.yml:/kong.yml environment: - KONG_DATABASE=off - KONG_DECLARATIVE_CONFIG=/kong.yml ports: - 8000:8000
-
创建
kong.yml
内容如下:这里从 public-apis 里随便找一个免费无须认证的 api 接口做测试
_format_version: "2.1" services: - name: test url: http://dog-facts-api.herokuapp.com/api/v1/resources/dogs routes: - name: search paths: - /get strip_path: true
-
docker-compose up
运行服务 -
curl http://localhost:8000/get?number=2
测试是否正常返回结果
Kong 插件开发环境搭建
目录结构
kong 插件的开发目录结构需要使用下面的格式,否则会报错 ... <plugin-name> plugin is enabled but not installed
:
<plugin-name>
├── kong
│ └── plugins
│ └── <plugin-name>
│ ├── handler.lua
│ └── schema.lua
└── <plugin-name>-<version>.rockspec
最后,我们本地目录长这个样子:
kong-plugin-dev
├── docker-compose.yml
├── kong.yml
└── plugins
└── disable-access-ip
├── disable-access-ip-0.0.1-1.rockspec
└── kong
└── plugins
└── disable-access-ip
├── handler.lua
└── schema.lua
disable-access-ip-0.0.1-1.rockspec
写入如下内容:
package = "disable-access-ip"
version = "0.0.1-1"
build = {
type = "builtin",
modules = {
["kong.plugins.disable-access-ip.handler"] = "kong/plugins/disable-access-ip/handler.lua",
["kong.plugins.disable-access-ip.schema"] = "kong/plugins/disable-access-ip/schema.lua"
}
}
schema.lua
写入如下内容:
local schema = {
name="disable-access-ip",
fields = {}
}
return schema
handler.lua
写入如下内容:
local handler = {
VERSION = "0.0.1-1",
PRIORITY = 10,
}
function handler:access(config)
kong.log("access")
end
return handler
除了上述俩个 lua 模块文件,其他的模块文件及其作用可见 官网文档
安装并加载插件
官网推荐使用 luarocks
来安装插件,根据 官网文档 所述,有三种方式:
- 法一:本地开发插件,然后通过
luarocks pack
打包得到.rock
文件,然后在 kong 的运行环境(即你的服务器或是 docker 环境)下通过luarocks install <path>
安装 .rock( path 可以是 url 或是本地路径) - 法二:在 kong 的运行环境里通过
luarocks make
打包并安装插件源码 - 法三:修改 kong 的
lua_package_path
配置让 kong 自行处理
本文使用法三,既不需要本地环境下安装 luarocks
,也不会污染 kong 运行环境里的相关包,毕竟这个插件仅是服务于 kong 的:
-
先更新下
docker-compose.yml
:version: "3.8" services: kong: image: kong:2.5.1 volumes: - ./kong.yml:/kong.yml - ./plugins:/plugins # <--------- new environment: - KONG_DATABASE=off - KONG_DECLARATIVE_CONFIG=/kong.yml # ↓↓↓↓↓↓↓ new ↓↓↓↓↓↓↓ - KONG_LUA_PACKAGE_PATH=/plugins/disable-access-ip/?.lua;; - KONG_PLUGINS=disable-access-ip - KONG_LOG_LEVEL=debug # ↑↑↑↑↑↑↑ new ↑↑↑↑↑↑↑ ports: - 8000:8000
-
在
kong.yml
里启用这个插件:services: # ... # ↓↓↓↓↓↓↓ new ↓↓↓↓↓↓↓ plugins: - name: disable-access-ip
-
重新运行
docker-compose up
如果日志打印 "... [debug] 1#0: [lua] plugins.lua:245: load_plugin(): Loading plugin: disable-access-ip" 即表明插件加载成功
编写具体逻辑
现在插件开发的基本环境已经 OK 了,下面开始具体功能的开发。
handler.lua
是我们主要用到的模块文件,kong 会将里面的逻辑注入到对应的执行阶段,共有7个阶段,不同阶段分别做什么可参见 官网文档 。
该插件则关注的是 access 阶段,该阶段会在客户端的请求转发到上游服务之前被执行, 我们通过调用全局变量 kong
的相关属性和方法来实现相关逻辑,其具体属性和函数可参见 官网PDK文档
另外,除了调用 PDK,我们也是可以直接调用 lua-nginx-module 里的 API 哦,毕竟 kong 是基于 Openresty 开发的,但对于修改类的操作,请尽可能使用 PDK 提供的修改函数,避免一些非预期情况的发生。
下面是完整的 handler.lua
内容:
local handler = {
VERSION = "0.0.1-1",
PRIORITY = 10,
}
-- 该函数用来判断输入内容是否是ipv4,ipv6字符串
-- copied from https://stackoverflow.com/questions/10975935/lua-function-check-if-ipv4-or-ipv6-or-string
local R = {ERROR = 0, IPV4 = 1, IPV6 = 2, STRING = 3}
local function getIPType(ip)
if type(ip) ~= "string" then return R.ERROR end
local chunks = {ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")}
if #chunks == 4 then
for _,v in pairs(chunks) do
if tonumber(v) > 255 then return R.STRING end
end
return R.IPV4
end
local chunks = {ip:match("^"..(("([a-fA-F0-9]*):"):rep(8):gsub(":$","$")))}
if #chunks == 8
or #chunks < 8 and ip:match('::') and not ip:gsub("::","",1):match('::') then
for _,v in pairs(chunks) do
if #v > 0 and tonumber(v, 16) > 65535 then return R.STRING end
end
return R.IPV6
end
return R.STRING
end
function handler:access(config)
local host = kong.request.get_host()
local hostType = getIPType(host)
-- 如果是ip地址,则返回444
if hostType == R.IPV4 or hostType == R.IPV6 then
return kong.response.error(403)
end
end
return handler
然后我们重新运行 docker-compose up
接着开始验证插件是否正常运行:
先通过域名访问:curl http://localhost:8000/get?number=2
正常,
然后通过 ip 访问:curl http://127.0.0.1:8000/get?number=2
会发现 kong 日志的状态码返回的是403,即我们的插件正常运行了。
结束
除了使用 lua 语言开发插件,kong 也支持使用 go,js,python 进行插件开发, 不过开发这种精简小插件就甭费神折腾其他语言的开发环境了,Language is just a tool, choose the one which is faster for delivery.