Lua是一款轻量级的开发语言,主要用于增强应用能力的脚本编写,其中也包括Nginx这样的Web服务器。

LuaJIT是非常快速的Lua解释器,能够快速解释Lua代码,非常适合Nginx这样同样高性能的应用,通常Nginx下的Lua开发会使用OpenResty(其适用LuaJIT的分支项目LuaJIT2)。

在Nginx下也可以直接进行Lua脚本开发,以Ubuntu为例,在已经安装Nginx之后执行以下命令安装Lua以及LuaJIT:

1
2
3
sudo apt install lua5.2 liblua5.2-dev

sudo apt install luajit

安装完毕之后,需要检查是否在Nginx中加载了ndk_http_module和ngx_http_lua_module模块,如果是手动加载,需要在nginx.conf中的event段前添加:

1
include modules-enabled/*.conf;

或者

1
2
3
load_module modules/ndk_http_module.so;

load_module modules/ngx_http_lua_module.so;

Nginx下开发Lua主要用到了Nginx的Lua指令以及Nginx API,具体可参考https://www.nginx.com/resources/wiki/modules/lua

对于简短的Lua代码可以使用content_by_lua或content_by_lua_block指令,在Nginx配置文件server段中直接编写,较长的Lua代码可以使用content_by_lua_file指定Lua文件,该文件的相对路径是基于Nginx的prefix路径,可以使用Nginx的API函数ngx.config.prefix()打印该路径。

比如通过Lua自动添加安全响应头,在Nginx的prefix目录下创建lua/sec_header.lua文件:

1
2
3
4
5
6
7
8
9
ngx.header["X-Content-Type-Options"] = "nosniff";

ngx.header["Content-Security-Policy"] = "upgrade-insecure-requests; script-src https: 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.staticfile.org;";

ngx.header["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";

ngx.header["X-Frame-Options"] = "SAMEORIGIN";

ngx.header["X-XSS-Protection"] = "1; mode=block";

然后在Nginx的server段中配置:

1
access_by_lua_file lua/sec_header.lua;

之后访问所有页面都会默认添加安全响应头,修改响应头信息只需要修改sec-header.lua文件即可。

又比如通过Lua和Redis来阻止特定的IP地址访问,将黑名单IP地址存储在Redis中,从Lua中读取黑名单IP进行源IP判断,如果源IP在名单中则阻止访问。

首先需要下载https://github.com/openresty/lua-resty-redis/blob/master/lib/resty/redis.lua文件到/usr/local/lib/lua-redis/lib目录下。

而后,在Nginx的配置文件nginx.conf中的HTTP段增加以下内容,用于引入上面下载的lua包文件:

1
lua_package_path "/usr/local/lib/lua-redis/lib/?.lua;;";

接着,在上文的Lua脚本所在目录下创建block_ips.lua文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
local redis = require "resty.redis"
local red = redis:new()

-- Connect to Redis server
red:set_timeout(1000)

local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis connection error")
return
end

local count, err = red:get_reused_times()
if 0 == count then
ok, err = red:auth("password")
if not ok then
ngx.log(ngx.ERR, "Auth failed with: " .. err)
return
end
elseif err then
ngx.log(ngx.ERR, "Failed to get reused times: " .. err)
return
end

-- Get current remote address
local ip = ngx.var.remote_addr

-- Use Redis command to check if current IP is in blocked ip list
local res, err = red:sismember("blocked_ips", ip)

if res == 1 then
-- if current IP is in blocked ip list, return 403 error
ngx.exit(ngx.HTTP_FORBIDDEN)
end

red:close()

当前版本的Redis默认启用身份认证,在配置文件Redis.conf中可以更改配置,认证口令也在该文件中记录。上述代码中使用red:auth对Redis服务做身份验证,避免后续的集合查询命令无法执行。

通过在Redis的集合blocked_ips中增、删想要阻止访问的IP地址即可实现阻止多IP地址访问。

最后,在Nginx的站点配置中将该Lua脚本加载,上文中的access_lua_by_file不允许多次使用,但可以使用loadfile指令起到多个文件加载的效果:

1
2
3
4
access_by_lua_block {
loadfile('/usr/share/nginx/lua/sec_header.lua')();
loadfile('/usr/share/nginx/lua/block_ips.lua')();
}

通过以上指令可以让站点加载上述两个Lua脚本,达到启用安全响应头和阻止恶意IP的效果。