https://github.com/fffonion/lua-resty-openssl
支持OpenSSL 1.1.x, 1.0.x和1.0.2系列
https://github.com/fffonion/lua-resty-openssl
支持OpenSSL 1.1.x, 1.0.x和1.0.2系列
![]()
使用opm:
|
1 |
opm install fffonion/lua-resty-acme |
opm中沒有luaossl庫,所以這種安裝使用的是基於FFI的Openssl後端;需要OpenResty鏈接了大於等於1.1版本的OpenSSL。
也可以使用luarocks安裝:
|
1 |
luarocks install lua-resty-acme |
以/etc/openresty目錄為例,如果目錄不存在,請自行修改。
生成一個賬戶密鑰
|
1 |
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out /etc/openresty/account.key |
生成一個默認證書
|
1 |
openssl req -newkey rsa:2048 -nodes -keyout /etc/openresty/default.key -x509 -days 365 -out /etc/openresty/default.pem |
在Nginx配置的http 節插入以下內容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
resolver 8.8.8.8; lua_shared_dict acme 16m; init_by_lua_block { require("resty.acme.autossl").init({ -- setting the following to true -- implies that you read and accepted https://letsencrypt.org/repository/ tos_accepted = true, -- uncomment following for first time setup -- staging = true, -- uncomment folloing to enable RSA + ECC double cert -- domain_key_types = { 'rsa', 'ecc' }, account_key_path = "/etc/openresty/account.key", account_email = "此處填寫郵箱", domain_whitelist = { "你的域名1", "你的域名2" }, }) } init_worker_by_lua_block { require("resty.acme.autossl").init_worker() } |
首次配置時,建議將init_by_lua_block中的staing = true取消注釋,以防錯誤過多觸發限流;測試通過後再加回注釋使用生產API。
在需要使用證書的server節插入
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
server { server_name example.com; # required to verify Let's Encrypt API lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; lua_ssl_verify_depth 2; # fallback certs, make sure to create them before hand ssl_certificate /etc/openresty/default.pem; ssl_certificate_key /etc/openresty/default.key; ssl_certificate_by_lua_block { require("resty.acme.autossl").ssl_certificate() } location /.well-known { content_by_lua_block { require("resty.acme.autossl").serve_http_challenge() } } } |
CentOS/Fedora等系統的根證書在/etc/ssl/certs/ca-bundle.crt,請根據實際情況修改lua_ssl_trusted_certificate。
保存後,reload nginx。
在一般情況下,domain_whitelist必須配置,以防止惡意請求通過偽造SNI頭進行拒絕服務攻擊。
如果要匹配一系列域名,可以使用__index來實現。比如下面的例子僅匹配example.com的子域名:
|
1 2 3 |
domain_whitelist = setmetatable({}, { __index = function(_, k) return ngx.re.match(k, [[\.example\.com$]], "jo") end}), |
將init_by_lua_block中的domain_key_types = { 'rsa', 'ecc' }取消注釋後,即可同時申請兩套證書。
為了讓申請到證書前的握手不出錯斷開,給Nginx配置默認的ECC證書
|
1 2 3 |
openssl ecparam -name prime256v1 -genkey -out /etc/openresty/default-ecc.key openssl req -new -sha256 -key /etc/openresty/default-ecc.key -subj "/" -out temp.csr openssl x509 -req -sha256 -days 365 -in temp.csr -signkey /etc/openresty/default-ecc.key -out /etc/openresty/default-ecc.pem |
然後在server節中原有的ssl_certificate下增加兩行
|
1 2 |
ssl_certificate /etc/openresty/default-ecc.pem; ssl_certificate_key /etc/openresty/default-ecc.key; |
關於加密套件等的選擇可藉助搜索引擎。
0.5.0開始支持tls-alpn-01,可以支持在只開放443端口的環境里完成驗證。方法是通過蜜汁FFI偏移找到當前請求的SSL結構,然後設置了新的ALPN。需要多個stream server多次proxy,拓撲結構如下:
|
1 2 3 4 5 |
[stream unix:/tmp/nginx-tls-alpn.sock ssl] Y / [stream 443] --- ALPN是acme-tls ? N \ [http unix:/tmp/nginx-default.sock ssl] |
第一個stream server打開了443端口,根據請求的ALPN分發到不同的後段;如果是acme-tls則轉發到我們的庫,否則轉發到正常的https。
示例配置見Github。
實現了一個端口服務復用的透明代理,可以在同一個端口上運行多個協議。根據每次連接中客戶端發起的首個請求檢測協議,根據協議或各種條件選擇代理的上游。
需要打一個補丁。由@fcicq在這個討論中貢獻。這個補丁實現了BSD的socket recv()語義。目前官方也有這個feature的PR。
示例配置:
|
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 |
stream { init_by_lua_block { local mul = require("resty.multiplexer") mul.load_protocols( "http", "ssh", "dns", "tls", "xmpp" ) mul.set_rules( {{"client-host", "10.0.0.1"}, "internal-host", 80}, {{"protocol", "http"}, {"client-host", "10.0.0.2"}, "internal-host", 8001}, {{"protocol", "http"}, "example.com", 80}, {{"protocol", "ssh"}, "github.com", 22}, {{"protocol", "dns"}, "1.1.1.1", 53}, {{"protocol", "tls"}, {"time", nil}, "twitter.com", 443}, {{"protocol", "tls"}, "www.google.com", 443}, {{"default", nil}, "127.0.0.1", 80} ) mul.matcher_config.time = { minute_match = {0, 30}, minute_not_match = {{31, 59}}, } } resolver 8.8.8.8; server { listen 80; content_by_lua_block { local mul = require("resty.multiplexer") local mp = mul:new() mp:run() } } } |
示例中服務監聽在80端口,並定義規則:
10.0.0.1,代理到 internal-host.com:80HTTP 而且客戶端來自10.0.0.2,代理到 internal-host:8001SSH,代理到 github.com:22DNS,代理到 1.1.1.1:53SSL/TLS 而且現在的時間是 0 到 30分,代理到 twitter.com:443SSL/TLS 而且現在的時間是 31 到 59分,代理到 www.google.com:443基於OpenResty ,MaxMind GeoIP數據庫和從bgp.he.net生成的ASN數據庫,因為沒有經緯度的需求所以沒有顯示。下文有源代碼的鏈接,如果需要可以自行修改加上經緯度或者將輸出變為JSON等。
這裡有chrome插件
查詢當前IPv4地址
$ curl http://cidr.me/ip
x.x.x.x
查詢當前IPv6地址
$ curl http://ipv6.cidr.me/ip
x.x.x.x
查詢當前IP的PTR記錄(反向DNS),地理位置和ASN
$ curl http://cidr.me/
x.x.x.x
Country, City
ASN number
查詢當前IP的PTR記錄
$ curl http://cidr.me/rdns
x-x-x-x.com
查詢指定IP的地理位置
$ curl http://cidr.me/74.125.203.199
74.125.203.199 th-in-f199.1e100.net
United States, California, Mountain View
AS15169 Google Inc.
查詢域名的地理位置
$ curl http://cidr.me/www.google.com.hk
172.217.3.195 sea15s12-in-f3.1e100.net
United States, California, Mountain View
AS15169 Google LLC2607:f8b0:400a:809::2003 sea15s12-in-x03.1e100.net
United States
AS15169 Google LLC
查詢域名的IP
$ curl http://cidr.me/www.google.com.hk/ip
74.125.203.199
2607:f8b0:400a:809::2003
查詢域名的IP和CNAME(如果存在)
$ curl http://cidr.me/www.youtube.com/dns
youtube-ui.l.google.com 172.217.3.174
172.217.3.206
216.58.193.78
216.58.217.46
2607:f8b0:400a:808::200e
在這裡https://gist.github.com/fffonion/44e5fb59e2a8f0efba5c1965c6043584
需要ngx_http_geoip_module, echo-nginx-module, lua-nginx-module,安裝libgeoip,並將maxmind的舊版geoip數據庫放在/usr/share/GeoIP
功能類似於dlundquist/sniproxy
推薦 OpenResty 加上 stream 模塊和 ngx_stream_lua_module 模塊。在 1.9.15.1 上測試通過。

示例配置:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
stream { lua_resolver 8.8.8.8; init_worker_by_lua_block { sni_rules = { ["www.google.com"] = {"www.google.com", 443}, ["www.facebook.com"] = {"9.8.7.6", 443}, ["twitter.com"] = {"1.2.3.4"}, [".+.twitter.com"] = {nil, 443} } } server { error_log /var/log/nginx/sniproxy-error.log error; listen 443; content_by_lua_block { local sni = require("resty.sniproxy") local sp = sni:new() sp:run() } } } |
A Lua table sni_rules should be defined in the init_worker_by_lua_block directive.
The key can be either whole host name or regular expression. Use . for a default host name. If no entry is matched, connection will be closed.
The value is a table containing host name and port. If host is set to nil, the server_name in SNI will be used. If the port is not defined or set to nil, 443 will be used.
Rules are applied with the priority as its occurrence sequence in the table. In the example above, twitter.com will match the third rule rather than the fourth.
If the protocol version is less than TLSv1 (eg. SSLv3, SSLv2), connection will be closed, since SNI extension is not supported in these versions.