Skip to content

Commit ecc5084

Browse files
committed
feature: added new boolean-value options "ssl" and "ssl_verify" to the connect() method connecting to MySQL via SSL.
1 parent ada4920 commit ecc5084

File tree

3 files changed

+210
-11
lines changed

3 files changed

+210
-11
lines changed

README.markdown

+12
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,18 @@ The `options` argument is a Lua table holding the following keys:
205205
* `max_packet_size`
206206

207207
the upper limit for the reply packets sent from the MySQL server (default to 1MB).
208+
* `ssl`
209+
210+
If set to `true`, then uses SSL to connect to MySQL (default to `false`). If the MySQL
211+
server does not have SSL support
212+
(or just disabled), the error string "ssl disabled on server" will be returned.
213+
* `ssl_verify`
214+
215+
If set to `true`, then verifies the validity of the server SSL certificate (default to `false`).
216+
Note that you need to configure the [lua_ssl_trusted_certificate](https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate)
217+
to specify the CA certificate used by your MySQL server. You may also
218+
need to configure [lua_ssl_verify_depth](https://github.com/openresty/lua-nginx-module#lua_ssl_verify_depth)
219+
accordingly.
208220
* `pool`
209221

210222
the name for the MySQL connection pool. if omitted, an ambiguous pool name will be generated automatically with the string template `user:database:host:port` or `user:database:path`. (this option was first introduced in `v0.08`.)

lib/resty/mysql.lua

+48-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ local tcp = ngx.socket.tcp
77
local strbyte = string.byte
88
local strchar = string.char
99
local strfind = string.find
10+
local format = string.format
1011
local strrep = string.rep
1112
local null = ngx.null
1213
local band = bit.band
@@ -23,6 +24,14 @@ local error = error
2324
local tonumber = tonumber
2425

2526

27+
if not ngx.config
28+
or not ngx.config.ngx_lua_version
29+
or ngx.config.ngx_lua_version < 9011
30+
then
31+
error("ngx_lua 0.9.11+ required")
32+
end
33+
34+
2635
local ok, new_tab = pcall(require, "table.new")
2736
if not ok then
2837
new_tab = function (narr, nrec) return {} end
@@ -38,6 +47,7 @@ local STATE_CONNECTED = 1
3847
local STATE_COMMAND_SENT = 2
3948

4049
local COM_QUERY = 0x03
50+
local CLIENT_SSL = 0x0800
4151

4252
local SERVER_MORE_RESULTS_EXISTS = 8
4353

@@ -136,7 +146,7 @@ local function _dump(data)
136146
local len = #data
137147
local bytes = new_tab(len, 0)
138148
for i = 1, len do
139-
bytes[i] = strbyte(data, i)
149+
bytes[i] = format("%x", strbyte(data, i))
140150
end
141151
return concat(bytes, " ")
142152
end
@@ -175,11 +185,13 @@ local function _send_packet(self, req, size)
175185

176186
self.packet_no = self.packet_no + 1
177187

178-
--print("packet no: ", self.packet_no)
188+
-- print("packet no: ", self.packet_no)
179189

180190
local packet = _set_byte3(size) .. strchar(self.packet_no) .. req
181191

182-
--print("sending packet...")
192+
-- print("sending packet: ", _dump(packet))
193+
194+
-- print("sending packet... of size " .. #packet)
183195

184196
return sock:send(packet)
185197
end
@@ -562,9 +574,10 @@ function _M.connect(self, opts)
562574
pos = pos + 9 -- skip filler
563575

564576
-- two lower bytes
565-
self._server_capabilities, pos = _get_byte2(packet, pos)
577+
local capabilities -- server capabilities
578+
capabilities, pos = _get_byte2(packet, pos)
566579

567-
--print("server capabilities: ", self._server_capabilities)
580+
-- print(format("server capabilities: %#x", capabilities))
568581

569582
self._server_lang = strbyte(packet, pos)
570583
pos = pos + 1
@@ -578,10 +591,9 @@ function _M.connect(self, opts)
578591
local more_capabilities
579592
more_capabilities, pos = _get_byte2(packet, pos)
580593

581-
self._server_capabilities = bor(self._server_capabilities,
582-
lshift(more_capabilities, 16))
594+
capabilities = bor(capabilities, lshift(more_capabilities, 16))
583595

584-
--print("server capabilities: ", self._server_capabilities)
596+
--print("server capabilities: ", capabilities)
585597

586598
-- local len = strbyte(packet, pos)
587599
local len = 21 - 8 - 1
@@ -598,13 +610,38 @@ function _M.connect(self, opts)
598610
scramble = scramble .. scramble_part2
599611
--print("scramble: ", _dump(scramble))
600612

613+
local client_flags = 0x3f7cf;
614+
615+
local ssl_verify = opts.ssl_verify
616+
local use_ssl = opts.ssl or ssl_verify
617+
618+
if use_ssl then
619+
if band(capabilities, CLIENT_SSL) == 0 then
620+
return nil, "ssl disabled on server"
621+
end
622+
623+
-- send a SSL Request Packet
624+
local req = _set_byte4(bor(client_flags, CLIENT_SSL))
625+
.. _set_byte4(self._max_packet_size)
626+
.. "\0" -- TODO: add support for charset encoding
627+
.. strrep("\0", 23)
628+
629+
local packet_len = 4 + 4 + 1 + 23
630+
local bytes, err = _send_packet(self, req, packet_len)
631+
if not bytes then
632+
return nil, "failed to send client authentication packet: " .. err
633+
end
634+
635+
local ok, err = sock:sslhandshake(false, nil, ssl_verify)
636+
if not ok then
637+
return nil, "failed to do ssl handshake: " .. (err or "")
638+
end
639+
end
640+
601641
local password = opts.password or ""
602642

603643
local token = _compute_token(password, scramble)
604644

605-
-- local client_flags = self._server_capabilities
606-
local client_flags = 260047;
607-
608645
--print("token: ", _dump(token))
609646

610647
local req = _set_byte4(client_flags)

t/ssl.t

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# vim:set ft= ts=4 sw=4 et:
2+
3+
use Test::Nginx::Socket::Lua;
4+
use Cwd qw(cwd);
5+
6+
repeat_each(2);
7+
8+
plan tests => repeat_each() * (3 * blocks());
9+
10+
my $pwd = cwd();
11+
12+
our $HttpConfig = qq{
13+
resolver \$TEST_NGINX_RESOLVER;
14+
lua_package_path "$pwd/lib/?.lua;$pwd/t/lib/?.lua;;";
15+
lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;";
16+
};
17+
18+
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
19+
$ENV{TEST_NGINX_MYSQL_PORT} ||= 3306;
20+
$ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1';
21+
$ENV{TEST_NGINX_MYSQL_PATH} ||= '/var/run/mysql/mysql.sock';
22+
23+
#log_level 'warn';
24+
25+
no_long_string();
26+
no_shuffle();
27+
check_accum_error_log();
28+
29+
run_tests();
30+
31+
__DATA__
32+
33+
=== TEST 1: send query w/o result set
34+
--- http_config eval: $::HttpConfig
35+
--- config
36+
location /t {
37+
content_by_lua '
38+
local mysql = require "resty.mysql"
39+
local db = mysql:new()
40+
41+
db:set_timeout(1000) -- 1 sec
42+
43+
local ok, err, errno, sqlstate = db:connect({
44+
host = "$TEST_NGINX_MYSQL_HOST",
45+
port = $TEST_NGINX_MYSQL_PORT,
46+
database = "ngx_test",
47+
user = "ngx_test",
48+
password = "ngx_test",
49+
ssl = true,
50+
})
51+
52+
if not ok then
53+
ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)
54+
return
55+
end
56+
57+
ngx.say("connected to mysql ", db:server_ver(), ".")
58+
59+
local bytes, err = db:send_query("drop table if exists cats")
60+
if not bytes then
61+
ngx.say("failed to send query: ", err)
62+
end
63+
64+
ngx.say("sent ", bytes, " bytes.")
65+
66+
local res, err, errno, sqlstate = db:read_result()
67+
if not res then
68+
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
69+
end
70+
71+
local ljson = require "ljson"
72+
ngx.say("result: ", ljson.encode(res))
73+
74+
local ok, err = db:close()
75+
if not ok then
76+
ngx.say("failed to close: ", err)
77+
return
78+
end
79+
';
80+
}
81+
--- request
82+
GET /t
83+
--- response_body_like chop
84+
^connected to mysql \d\.\S+\.
85+
sent 30 bytes\.
86+
result: \{"affected_rows":0,"insert_id":0,"server_status":2,"warning_count":[01]\}$
87+
--- no_error_log
88+
[error]
89+
90+
91+
92+
=== TEST 2: send query w/o result set (verify)
93+
--- http_config eval: $::HttpConfig
94+
--- config
95+
lua_ssl_trusted_certificate /etc/test.crt; # assuming used by the MySQL server
96+
location /t {
97+
content_by_lua '
98+
local mysql = require "resty.mysql"
99+
local db = mysql:new()
100+
101+
db:set_timeout(1000) -- 1 sec
102+
103+
local ok, err, errno, sqlstate = db:connect({
104+
host = "$TEST_NGINX_MYSQL_HOST",
105+
port = $TEST_NGINX_MYSQL_PORT,
106+
database = "ngx_test",
107+
user = "ngx_test",
108+
password = "ngx_test",
109+
ssl = true,
110+
ssl_verify = true,
111+
})
112+
113+
if not ok then
114+
ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)
115+
return
116+
end
117+
118+
ngx.say("connected to mysql ", db:server_ver(), ".")
119+
120+
local bytes, err = db:send_query("drop table if exists cats")
121+
if not bytes then
122+
ngx.say("failed to send query: ", err)
123+
end
124+
125+
ngx.say("sent ", bytes, " bytes.")
126+
127+
local res, err, errno, sqlstate = db:read_result()
128+
if not res then
129+
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
130+
end
131+
132+
local ljson = require "ljson"
133+
ngx.say("result: ", ljson.encode(res))
134+
135+
local ok, err = db:close()
136+
if not ok then
137+
ngx.say("failed to close: ", err)
138+
return
139+
end
140+
';
141+
}
142+
--- request
143+
GET /t
144+
--- response_body_like chop
145+
^connected to mysql \d\.\S+\.
146+
sent 30 bytes\.
147+
result: \{"affected_rows":0,"insert_id":0,"server_status":2,"warning_count":[01]\}$
148+
--- no_error_log
149+
[error]
150+

0 commit comments

Comments
 (0)