簡介
在官網上對 OpenResty 是這樣介紹的(http://openresty.org):
“OpenResty 是一個基於 Nginx 與 Lua 的高性能 Web 平台,其內部集成了大量精良的 Lua 庫、第三方模塊以及大多數的依賴項。用於方便地搭建能夠處理超高併發、擴展性極高的動態 Web 應用、Web 服務和動態網關。”
“OpenResty 通過匯聚各種設計精良的 Nginx 模塊(主要由 OpenResty 團隊自主開發),從而將 Nginx 有效地變成一個強大的通用 Web 應用平台。這樣,Web 開發人員和系統工程師可以使用 Lua 腳本語言調動 Nginx 支持的各種 C 以及 Lua 模塊,快速構造出足以勝任 10K 乃至 1000K 以上單機併發連接的高性能 Web 應用系統。”
“OpenResty 的目標是讓你的 Web 服務直接跑在 Nginx 服務內部,充分利用 Nginx 的非阻塞 I/O 模型,不僅僅對 HTTP 客户端請求,甚至於對遠程後端諸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都進行一致的高性能響應。”
從以上官網描述裏我們可以知道,OpenResty 官網對其定位是以 Nginx 為核心集成 Lua,打造一個兼具開發效率和高性能的服務端開發平台。
OpenResty 的核心是基於 Nginx 的一個 C 模塊(lua-Nginx-module),該模塊將 LuaJIT 嵌入到 Nginx 服務器中,並對外提供一套完整的 Lua API,透明地支持非阻塞 I/O,提供了輕量級線程、定時器等高級抽象。
我們可以用 Lua 語言來進行字符串和數值運算、查詢數據庫、發送 HTTP 請求、執行定時任務、調用外部命令等,還可以用 FFI 的方式調用外部 C 函數。這基本上可以滿足服務端開發需要的所有功能。
掌握好了 OpenResty,我們就可以同時擁有腳本語言的開發效率和迭代速度,以及 Nginx C 模塊的高併發和高性能優勢。
下面為大家介紹本文大綱:
- OpenResty 的 hello world 該怎麼寫
- 快速上手 Lua 腳本語言
- OpenResty 用到的 Nginx 知識
- OpenResty 在網關安全中如何應用
OpenResty 的 hello world 該怎麼寫
OpenResty 的安裝
OpenResty 的安裝有多種方法,比如使用操作系統的包管理器、源碼編譯或者 docker 鏡像。推薦優先使用 yum、apt-get、brew 這類包管理系統,來安裝 OpenResty。
對於 Mac OS X 或 macOS 用户,強烈推薦您使用 homebrew 包管理工具安裝 OpenResty。可以直接使用下面 這一條命令:
brew install openresty/brew/openresty
對於一些常見的 Linux 發行版本(Ubuntu、Debian、CentOS、RHEL、Fedora、OpenSUSE、Alpine 和 Amazon Linux), OpenResty 提供 官方預編譯包。確保首先用這種方式來安裝。這裏用 CentOS 舉例,可以使用如下方式,
CentOS 9 或者更新版本
# add the yum repo:
wget https://openresty.org/package/centos/openresty2.repo
sudo mv openresty2.repo /etc/yum.repos.d/openresty.repo
# update the yum index:
sudo yum check-update
CentOS 8 或者更老版本
# add the yum repo:
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/openresty.repo
# update the yum index:
sudo yum check-update
然後就可以像下面這樣安裝軟件包,比如 openresty:
sudo yum install -y openresty
Docker 安裝
Docker 安裝的方式就最為簡單了,只需要輸入以下命令,就可以獲取打包好的鏡像。
docker pull openresty/openresty
目錄結構
安裝 OpenResty 成功後的目錄結構如下(以默認安裝目錄為例):
/usr/local/openresty/ #安裝主目錄
├── bin #存放可執行文件
├── luajit #LuaJIT運行庫
├── lualib #Lua組件
├── Nginx #Nginx核心運行平台
├── pod #參考手冊(restydoc)使用的數據
└── site #包管理工具(opm)使用的數據
啓動服務
yum 安裝完後,就可以直接運行 openresty 命令,啓動 OpenResty 服務。
/usr/local/openresty/bin/openresty #啓動OpenResty服務
OpenResty 默認開啓了 localhost:80 服務,使用 wget 或者 curl 這樣的工具就可以驗證 OpenResty 是否正常工作:
curl http://localhost:80 #curl命令發送HTTP請求
下面是一些其他常用命令,
/usr/local/openresty/bin/openresty -s stop #停止 OpenResty 服務
/usr/local/openresty/bin/openresty -s reload #重新加載 Nginx 配置文件
/usr/local/openresty/bin/openresty -t #檢查 Nginx 配置文件是否正確
/usr/local/openresty/bin/openresty -c #指定配置文件啓動
OpenResty 的操作命令跟 Nginx 保持一致。可以執行 openresty -h 以及 nginx -h 對比看出,
命令行工具 resty
如果你想安裝命令行工具 resty,那麼可以像下面這樣安裝 openresty-resty 包:
sudo yum install -y openresty-resty
resty 是一個 cli 工具,可以使用 -e 參數可以在命令行裏直接執行 Lua 代碼,我們可以在命令行執行如下命令,
[root@VM-4-5-centos ~]# resty -e "print('hello world')"
hello OpenResty
resty 工具還有很多選項用於配置行為,非常靈活,-e 之外較常用的有
-c :指定最大併發連接數(默認值是64);
-I :指定Lua庫的搜索路徑;
-l :指定加載某個Lua庫;
--http-conf :定製在http域裏的指令;
--main-include :定製在main域裏的指令;
--shdict :定製使用的共享內存(參見10.2節);
--resolve-ipv6 :允許解析ipv6的地址。
想了解完整的列表,可以查看 resty -h 命令。
包管理工具 opm
跟大多數語言一樣有包管理工具一樣,OpenResty 也有自己的包管理工具 opm(OpenResty Package Manager),opm 在 openresty-opm 包裏,安裝命令如下,
sudo yum install -y openresty-opm
opm 是 OpenResty 自帶的包管理器,在你安裝好 OpenResty 之後,就可以直接使用。一些常見用法如下,
opm search http #搜索關鍵字http
opm search kafka #搜索關鍵字kafka
opm get agentzh/lua-resty-http #安裝組件,注意需要sudo
opm info agentzh/lua-resty-http #顯示組件的版本、作者等信息
opm remove agentzh/lua-resty-http #移除組件,同樣需要sudo
opm --install-dir=/opt get xxx #把組件安裝到/opt目錄下
opm --cwd get xxx #安裝到當前目錄的/resty_modules下
編寫 hello world
在上文中我們使用命令行工具 resty 寫了一個比較簡單的 OpenResty 程序,沒有 master 進程,也不會監聽端口。下面讓我們寫一個需要啓動 OpenResty 服務的 hello world。
首先找到 OpenResty 安裝目錄下 nginx/conf/nginx.conf 文件,在 server 下新增 OpenResty 的 content_by_lua 指令,裏面嵌入了 ngx.say 的代碼:
server {
listen 88;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
location /hello {
content_by_lua '
ngx.say("hello, world")
';
}
}
接着我們執行 openresty -s reload 命令,重新加載 nginx.conf 配置文件。沒有報錯的話,OpenResty 的服務就已經成功啓動了。
最後使用 curl 命令,來查看結果的返回:
[root@VM-4-5-centos conf]# curl localhost:88/hello
hello, world
到這裏,一個真正的 OpenResty 開發的 hello world 程序就完成了。
快速上手 Lua 腳本語言
Lua 環境
我們不用專門去安裝標準 Lua 5.1 之類的環境,因為 OpenResty 已經不再支持標準 Lua,而只支持 LuaJIT。這裏我介紹的 Lua 語法,也是和 LuaJIT 兼容的部分,而不是基於最新的 Lua 5.3,這一點需要特別注意。
在 OpenResty 的安裝目錄下,可以找到 LuaJIT 的目錄和可執行文件。在 CentOS 系統下,LuaJIT 的目錄如下,
[root@VM-4-5-centos luajit]# cd /usr/local/openresty/luajit/bin/
[root@VM-4-5-centos bin]# ll
total 536
lrwxrwxrwx 1 root root 18 Oct 12 11:22 luajit -> luajit-2.1.0-beta3
-rwxr-xr-x 1 root root 547728 Jul 18 12:38 luajit-2.1.0-beta3
我們可以執行 cp luajit /usr/local/bin/ 將 luajit 文件複製到 /usr/local/bin/ 目錄下,進而可以直接使用 luajit 命令。
查看 LuaJIT 的版本號,
[root@VM-4-5-centos ~]# luajit -v
LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2022 Mike Pall. https://luajit.org/
執行 lua 腳本,
[root@VM-4-5-centos ~]# echo 'print("hello world")' > 1.lua
[root@VM-4-5-centos ~]# cat 1.lua
print("hello world")
[root@VM-4-5-centos ~]# luajit 1.lua
hello world
[root@VM-4-5-centos ~]#
也可以使用 resty 來直接運行,它最終也是用 LuaJIT 來執行的,
[root@VM-4-5-centos ~]# resty -e 'print("hello world")'
hello world
基本語法
變量
在 Lua 中聲明變量,可以如下代碼所示,
local a = 'hello'
b = "world"
加了 local 關鍵字,用於聲明局部變量。
不加 local 關鍵字的話,變量默認是全局的。
註釋
兩個減號是單行註釋
-- 註釋
多行註釋
--[[
多行註釋
多行註釋
--]]
行尾結束
Lua 中代碼的行尾結束都不需要添加特殊字符,這跟 Java 不同(Java 在行尾需要添加 ;)。
local a = 'a'
print(a)
數據類型
Lua 中的數據類型不多,你可以通過 type 函數來返回一個值的類型,比如下面這樣的操作:
[root@VM-4-5-centos ~]# resty -e 'print(type("hello world"))
> print(type(print))
> print(type(true))
> print(type(360.0))
> print(type({}))
> print(type(nil))
> '
打印如下,
string
function
boolean
number
table
nil
這幾種就是 Lua 中的基本數據類型了。下面我們來簡單介紹一下它們。
字符串
在 Lua 中,有三種方式可以表達一個字符串:單引號、雙引號,以及長括號([[]]),示例如下,
新建 str.lua 文件,寫入以下內容,
local s = 'a'
local s1 = "b"
local s2 = [[c]]
print(s)
print(s1)
print(s2)
執行 luajit str.lua 返回結果如下,
a
b
c
在 Lua 中,字符串拼接採用 .. 的方式,示例如下,
編輯 str.lua 文件,寫入以下內容,
local s = 'a'
local s1 = "b"
local s2 = [[c]]
print(s)
print(s1)
print(s2)
local s3 =s .. s1 ..s2
print(s3)
執行 luajit str.lua 返回結果如下,
a
b
c
abc
布爾值
在 Lua 中,只有 nil 和 false 為假,其他都為 true,包括 0 和空字符串也為真。我們可以用示例印證一下:
新建 bool.lua 腳本文件,寫入以下內容,
local a = 0
local b
if a then
print("true")
end
a = ""
if a then
print("true")
end
print(b)
執行 luajit str.lua 返回結果如下,
true
true
nil
在 Lua 中,空值就是 nil。如果你定義了一個變量,但沒有賦值,它的默認值就是 nil,對應的就是上面示例代碼的局部變量 b。
數字
Lua 的 number 類型,是用雙精度浮點數來實現的。值得一提的是,LuaJIT 支持 dual-number(雙數)模式,也就是説,LuaJIT 會根據上下文來用整型來存儲整數,而用雙精度浮點數來存放浮點數。示例如下,
新建 number.lua 腳本文件,寫入以下內容,
print(type(2))
print(type(2.2))
print(type(0.2))
print(type(2e+1))
print(type(0.2e-1))
print(type(7.8263692594256e-06))
print(2 + 2)
print(2 + 22.2)
執行 luajit number.lua 返回結果如下,
number
number
number
number
number
number
4
24.2
函數
函數在 Lua 中是一等公民,你可以把函數存放在一個變量中,也可以當作另外一個函數的入參和出參。示例如下,
新建 fun.lua 文件,寫入以下代碼,
-- 階乘
function factorial1(n)
if n == 0 then
return 1
else
return n * factorial1(n - 1)
end
end
print(factorial1(5))
factorial2 = factorial1
print(factorial2(5))
執行 luajit fun.lua 返回結果如下,
120
120
分支控制
Lua 提供了以下兩種分支控制結構語句:
- if 語句
- if...else 語句
- if...elseif...else 語句
if 語句
Lua if 語句語法格式如下:
if(布爾表達式)
then
--[ 在布爾表達式為 true 時執行的語句 --]
end
以下是一個判斷變量 a 的值是否小於 20 的示例,
新建 if1.lua,寫入以下內容,
--[ 定義變量 --]
a = 10;
--[ 使用 if 語句 --]
if (a < 20) then
--[ if 條件為 true 時打印以下信息 --]
print("a 小於 20" );
end
print("a 的值為:", a);
執行 luajit if1.lua 返回結果如下,
a 小於 20
a 的值為: 10
if...else 語句
Lua if 語句可以與 else 語句搭配使用, 在 if 條件表達式為 false 時執行 else 語句代碼塊。
Lua if...else 語句語法格式如下:
if(布爾表達式)
then
--[ 布爾表達式為 true 時執行該語句塊 --]
else
--[ 布爾表達式為 false 時執行該語句塊 --]
end
以下是一個判斷變量 a 值的示例,
新建 if2.lua,寫入以下內容,
--[ 定義變量 --]
a = 100;
--[ 檢查條件 --]
if( a < 20 )
then
--[ if 條件為 true 時執行該語句塊 --]
print("a 小於 20" )
else
--[ if 條件為 false 時執行該語句塊 --]
print("a 大於 20" )
end
print("a 的值為 :", a)
執行 luajit if2.lua 返回結果如下,
a 大於 20
a 的值為 : 100
if...elseif...else 語句
Lua if 語句可以與 elseif...else 語句搭配使用, 在 if 條件表達式為 false 時執行 elseif...else 語句代碼塊,用於檢測多個條件語句。
Lua if...elseif...else 語句語法格式如下:
if( 布爾表達式 1)
then
--[ 在布爾表達式 1 為 true 時執行該語句塊 --]
elseif( 布爾表達式 2)
then
--[ 在布爾表達式 2 為 true 時執行該語句塊 --]
elseif( 布爾表達式 3)
then
--[ 在布爾表達式 3 為 true 時執行該語句塊 --]
else
--[ 如果以上布爾表達式都不為 true 則執行該語句塊 --]
end
以下是一個判斷變量 a 值的示例,
新建 if3.lua,寫入以下內容,
--[ 定義變量 --]
a = 100
--[ 檢查布爾條件 --]
if( a == 10 )
then
--[ 如果條件為 true 打印以下信息 --]
print("a 的值為 10" )
elseif( a == 20 )
then
--[ if else if 條件為 true 時打印以下信息 --]
print("a 的值為 20" )
elseif( a == 30 )
then
--[ if else if condition 條件為 true 時打印以下信息 --]
print("a 的值為 30" )
else
--[ 以上條件語句沒有一個為 true 時打印以下信息 --]
print("沒有匹配 a 的值" )
end
print("a 的真實值為: ", a )
執行 luajit if3.lua 返回結果如下,
沒有匹配 a 的值
a 的真實值為: 100
循環
Lua 編程語言中 for 循環語句可以重複執行指定語句,重複次數可在 for 語句中控制。
Lua 編程語言中 for 語句有兩大類:
- 數值 for 循環
- 泛型 for 循環
數值 for 循環
Lua 編程語言中數值 for 循環語法格式:
for var=exp1,exp2,exp3 do
<執行體>
end
var 從 exp1 變化到 exp2,每次變化以 exp3 為步長遞增 var,並執行一次 "執行體"。exp3 是可選的,如果不指定,默認為 1。示例如下,
新建 for1.lua 文件,寫入以下內容,
function f(x)
print("function")
return x*2
end
for i = 1, f(5) do print(i)
end
執行 luajit for1.lua 返回結果如下,
function
1
2
3
4
5
6
7
8
9
10
泛型 for 循環
泛型 for 循環通過一個迭代器函數來遍歷所有值,類似 java 中的 foreach 語句。
Lua 編程語言中泛型 for 循環語法格式:
--打印數組a的所有值
local a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end
i 是數組索引值,v 是對應索引的數組元素值。ipairs 是 Lua 提供的一個迭代器函數,用來迭代數組。
將以上內容下入 for2.lua 文件,打印結果如下,
1 one
2 two
3 three
Lua 模塊與包
模塊類似於一個封裝庫,從 Lua 5.1 開始,Lua 加入了標準的模塊管理機制,可以把一些公用的代碼放在一個文件裏,以 API 接口的形式在其他地方調用,有利於代碼的重用和降低代碼耦合度。
Lua 提供了一個名為 require 的函數用來加載模塊。要加載一個模塊,只需要簡單地調用就可以了。例如:
require("cjson")
-- 或者
require "cjson"
Lua 比較小巧,內置的標準庫並不多。在 OpenResty 的環境中默認支持了一些官方模塊,如 cjson 可以直接使用,其他的一些第三方庫則需要先使用 lua_package_path 指令配置 OpenResty 的文件尋址路徑,又或者直接使用 opm 包管理工具來安裝一些第三方模塊。
OpenResty 中默認啓用了下面列表的絕大部分組件,想要了解更多 OpenResty 相關組件的話,可以翻閲官網説明 https://openresty.org/cn/components.html。
LuaJIT
ArrayVarNginxModule
AuthRequestNginxModule
CoolkitNginxModule
DrizzleNginxModule
EchoNginxModule
EncryptedSessionNginxModule
FormInputNginxModule
HeadersMoreNginxModule
...
本文的 Lua 語法介紹到這裏就足夠在 OpenResty 中編寫 lua 腳本了,想要了解更多 Lua 內容,如 table、文件、調式等可以自行翻閲 https://www.runoob.com/lua/lua-tutorial.html 網站。
OpenResty 用到的 Nginx 知識
內置常量和變量
OpenResty 在內置 Lua 引擎中新增了一些常用的內置變量如下所示。
OpenResty 在內置 Lua 引擎中新增了一些常用的內置常量大致如下所示。
這些內置變量和常量都可以在 Lua 腳本中直接使用。
配置指令
OpenResty 定義了一系列 Nginx 配置指令,用於配置何時運行用户 Lua 腳本以及如何返回 Lua 腳本的執行結果,這些指令可以直接在 nginx.conf 配置文件中使用。
OpenResty 定義的 Nginx 配置指令大致如下所示。
這些指令中有 9 個 *_by_lua 指令,它們和 Nginx 的關係如下圖所示
其中,init_by_lua 只會在 Master 進程被創建時執行,init_worker_by_lua 只會在每個 Worker 進程被創建時執行。其他的 *_by_lua 指令則是由終端請求觸發,會被反覆執行。
所以在 init_by_lua 階段,我們可以預先加載 Lua 模塊和公共的只讀數據,這樣可以利用操作系統的 COW(copy on write)特性,來節省一些內存。
對於業務代碼來説,其實大部分的操作都可以在 content_by_lua 裏面完成,但更推薦的做法,是根據不同的功能來進行拆分,比如下面這樣:
- set_by_lua:設置變量;
- rewrite_by_lua:轉發、重定向等;
- access_by_lua:准入、權限等;
- content_by_lua:生成返回內容;
- header_filter_by_lua:應答頭過濾處理;
- body_filter_by_lua:應答體過濾處理;
- log_by_lua:日誌記錄。
利用這些階段的特性,我們可以一些通用邏輯進行拆分處理,比如我們可以在 access 階段解密,在 body filter 階段加密就可以了,在 content 階段的代碼是不用做任何修改的。
# 加密協議版本
location /test {
access_by_lua '...'; # 請求體解密
content_by_lua '...'; # 處理請求,不需要關心通信協議
body_filter_by_lua '...'; # 應答體加密
}
OpenResty 在網關安全中如何應用
WAF 介紹
Web 應用防火牆(Web Application Firewall,簡稱 WAF)對網站或者 App 的業務流量進行惡意特徵識別及防護,在對流量清洗和過濾後,將正常、安全的流量返回給服務器,避免網站服務器被惡意入侵導致性能異常等問題,從而保障網站的業務安全和數據安全。
常見 Web 應用攻擊防護
- 防禦一些常見常見威脅:SQL 注入、XSS 跨站、WebShell 上傳、後門攻擊、命令注入、非法 HTTP 協議請求、常見 Web 服務器漏洞攻擊、CSRF、核心文件非授權訪問、路徑穿越、網站被掃描等。
- CC 惡意攻擊防護:控制單一源 IP 的訪問頻率,基於重定向跳轉驗證、人機識別等。針對海量慢速請求攻擊,根據統計響應碼及 URL 請求分佈、異常 Referer 及 User-Agent 特徵識別,結合網站精準防護規則綜合防護。
- 網站隱身:不對攻擊者暴露站點地址,避免其繞過 Web 應用防火牆直接攻擊。
相關產品
目前 WAF 相關產品主要有三類:
- 硬件 WAF:效果好,但是貴!
- 軟件 WAF:效果還算可以,能用,有開源產品!
- 雲廠商 WAF:雲廠商的 WAF 都很貴!
鑑於極客精神(白嫖萬歲 😎),這裏介紹幾款業內開源的 WAF 產品,
- 長亭科技的雷池社區版,主頁地址:https://waf-ce.chaitin.cn/
- ModSecurity,主頁地址:https://www.modsecurity.org/
- Coraza,主頁地址:https://coraza.io/
- VeryNginx,主頁地址:https://github.com/alexazhou/VeryNginx
- NAXSI,主頁地址:https://github.com/nbs-system/naxsi
- NGX_WAF,主頁地址:https://github.com/ADD-SP/ngx_waf
- 南牆,主頁地址:https://waf.uusec.com/
- JANUSEC,主頁地址:https://www.janusec.com/
- HTTPWAF,主頁地址:https://github.com/httpwaf/httpwaf2.0
- 錦衣盾,主頁地址:https://www.jxwaf.com/
對於以上 WAF 產品的一些評價指標如下:
- 防護效果:主要是兩個維度,能不能防住攻擊,會不會影響普通用户
- 技術先進性:防護引擎的技術競爭力,是否具備對抗高級攻擊的能力
- 項目質量:本文將以功能完整性、開源代碼質量、文檔完整性等角度作為評價依據
- 社區認可度:反映了項目在用户社區中的聲譽和影響力,本文將以 GitHub Star 數作為評價依據
- 社區活躍度:是潛力的體現,活躍度越高發展越快,本文將以社區用户的參與度和作者維護項目的積極性作為
最終的的得分如下,
需要注意的是軟件 WAF 一般在第 7 層中進行防禦(osi 模型),並非能夠防禦所有類型的攻擊,比如 ddos 攻擊就不能防禦。不過一般雲廠商提供的 WAF 產品也有攜帶了 ddos 攻擊防禦的支持,比如阿里雲。
OpenResty 在 WAF 中的應用
使用 OpenResty 作為流量入口時,我們可以通過編寫一些 Lua 腳本來實現 WAF 防禦的功能。Lua 腳本可以在 Nginx 配置文件中指定,在不同的階段執行。
對於防火牆功能,我們通常可以在 access_by_lua 階段執行 Lua 腳本,用於匹配請求或響應的頭部或內容,並根據匹配結果決定是否放行數據包或返回錯誤信息。
下面我將給大家演示如何使用 OpenResty 實現一個基於 Lua 的 WAF(Web Application Firewall)功能。用來識別和阻止常見的 Web 攻擊,如 cc 防禦、ip 黑名單、ua 參數校驗等。
cc 防禦
- 修改 nginx.conf 文件,加入
access_by_lua_file cc.lua指令,
http {
# 聲明一個 10m 大小的共享內存 cc_dict
lua_shared_dict cc_dict 10m;
lua_package_path "/usr/local/openresty/nginx/conf/lua/waf/?.lua;/usr/local/openresty/lualib/?.lua;";
...
server {
listen 88;
server_name localhost;
# 在access階段執行 cc 防禦插件
access_by_lua_file cc.lua;
location / {
...
}
}
}
- 新建
cc.lua腳本,寫入以下內容,
-- 獲取客户端ip
local function getClientIp()
IP = ngx.var.remote_addr
if IP == nil then
IP = "unknown"
end
return IP
end
local function denyCC()
local uri=ngx.var.uri
ccCount=100
ccSeconds=6
local access_uri = getClientIp()..uri
local limit = ngx.shared.cc_dict
local req,_=limit:get(access_uri)
if req then
if req > ccCount then
ngx.exit(503)
return true
else
limit:incr(access_uri,1)
end
else
limit:set(access_uri,1,ccSeconds)
end
return false
end
if denyCC() then
return
end
- 重啓 OpenResty 服務,就完成了 cc 防禦功能。
openresty -s reload
ip 黑名單
- 修改 nginx.conf 文件,加入
access_by_lua_file ip_block.lua指令,
http {
lua_package_path "/usr/local/openresty/nginx/conf/lua/waf/?.lua;/usr/local/openresty/lualib/?.lua;";
...
server {
listen 88;
server_name localhost;
# 在access階段執行 ip_block 防禦插件
access_by_lua_file ip_block.lua;
location / {
...
}
}
}
- 新建
ip_block.lua腳本,寫入以下內容,
local cjson = require "cjson"
local function read_json(var)
file = io.open(var,"r")
if file==nil then
return
end
str = file:read("*a")
file:close()
list = cjson.decode(str)
return list
end
local function getClientIp()
IP = ngx.var.remote_addr
if IP == nil then
IP = "unknown"
end
return IP
end
local function blockIpCheck()
local ipBlockList=read_json('/usr/local/openresty/nginx/conf/lua/waf/ip_block.json')
if next(ipBlockList) ~= nil then
for _,ip in pairs(ipBlockList) do
if getClientIp()==ip then
ngx.exit(403)
return true
end
end
end
return false
end
if blockIpCheck() then
return
end
- 在
/usr/local/openresty/nginx/conf/lua/waf目錄下新建ip_block.json文件,寫入我們要加入黑名單的 ip,
["58.48.224.7"]
- 重啓 OpenResty 服務,就完成了 ip 黑名單功能。
openresty -s reload
ua 攔截
- 修改 nginx.conf 文件,加入
access_by_lua_file ua.lua指令,
http {
lua_package_path "/usr/local/openresty/nginx/conf/lua/waf/?.lua;/usr/local/openresty/lualib/?.lua;";
...
server {
listen 88;
server_name localhost;
# 在access階段執行 ua 防禦插件
access_by_lua_file ua.lua;
location / {
...
}
}
}
- 新建
ua.lua腳本,寫入以下內容,
local ngxMatch=ngx.re.match
local cjson = require "cjson"
local function read_json(var)
file = io.open(var,"r")
if file==nil then
return
end
str = file:read("*a")
file:close()
list = cjson.decode(str)
return list
end
function ua()
local ua = ngx.var.http_user_agent
local userAgents=read_json('/usr/local/openresty/nginx/conf/lua/waf/user_agent.json')
if next(userAgents) ~= nil then
for _,rule in pairs(userAgents) do
if rule ~="" and ngxMatch(ua,rule,"isjo") then
ngx.exit(403)
return true
end
end
end
return false
end
if ua() then
return
end
- 在
/usr/local/openresty/nginx/conf/lua/waf目錄下新建user_agent.json文件,寫入我們要加入黑名單的 ua 信息,
["Chrome/116.0.0.0"]
- 重啓 OpenResty 服務,就完成了 ua 攔截功能。
openresty -s reload
相關資料
- OpenResty 官網:https://openresty.org/cn/benchmark.html
- 菜鳥教程:https://www.runoob.com/lua/lua-tutorial.html
- 《OpenResty完全開發指南》:https://weread.qq.com/web/bookDetail/fec3240071848696fec3572
- 《OpenResty從入門到實戰》:https://time.geekbang.org/column/intro/186?code=hkx6qkdp47icc...
總結
自此本文介紹了OpenResty入門以及使用 Lua 腳本實現一些常見的網關安全功能等。需要注意的就是大家在已有的 Nginx 服務遷移到 OpenResty 上來時,記得注意 OpenResty 版本,Nginx 與 OpenResty 相同版本情況下,OpenResty 官方是保證完全兼容的。
最後感謝大家閲讀,希望本文能對你有所幫助。
關注公眾號【waynblog】每週分享技術乾貨、開源項目、實戰經驗、國外優質文章翻譯等,您的關注將是我的更新動力!