Skip to content

Commit

Permalink
feature: added new boolean-value options "ssl" and "ssl_verify" to th…
Browse files Browse the repository at this point in the history
…e connect() method connecting to MySQL via SSL.
  • Loading branch information
agentzh committed Aug 5, 2014
1 parent ada4920 commit ecc5084
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 11 deletions.
12 changes: 12 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ The `options` argument is a Lua table holding the following keys:
* `max_packet_size`

the upper limit for the reply packets sent from the MySQL server (default to 1MB).
* `ssl`

If set to `true`, then uses SSL to connect to MySQL (default to `false`). If the MySQL
server does not have SSL support
(or just disabled), the error string "ssl disabled on server" will be returned.
* `ssl_verify`

If set to `true`, then verifies the validity of the server SSL certificate (default to `false`).
Note that you need to configure the [lua_ssl_trusted_certificate](https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate)
to specify the CA certificate used by your MySQL server. You may also
need to configure [lua_ssl_verify_depth](https://github.com/openresty/lua-nginx-module#lua_ssl_verify_depth)
accordingly.
* `pool`

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`.)
Expand Down
59 changes: 48 additions & 11 deletions lib/resty/mysql.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ local tcp = ngx.socket.tcp
local strbyte = string.byte
local strchar = string.char
local strfind = string.find
local format = string.format
local strrep = string.rep
local null = ngx.null
local band = bit.band
Expand All @@ -23,6 +24,14 @@ local error = error
local tonumber = tonumber


if not ngx.config
or not ngx.config.ngx_lua_version
or ngx.config.ngx_lua_version < 9011
then
error("ngx_lua 0.9.11+ required")
end


local ok, new_tab = pcall(require, "table.new")
if not ok then
new_tab = function (narr, nrec) return {} end
Expand All @@ -38,6 +47,7 @@ local STATE_CONNECTED = 1
local STATE_COMMAND_SENT = 2

local COM_QUERY = 0x03
local CLIENT_SSL = 0x0800

local SERVER_MORE_RESULTS_EXISTS = 8

Expand Down Expand Up @@ -136,7 +146,7 @@ local function _dump(data)
local len = #data
local bytes = new_tab(len, 0)
for i = 1, len do
bytes[i] = strbyte(data, i)
bytes[i] = format("%x", strbyte(data, i))
end
return concat(bytes, " ")
end
Expand Down Expand Up @@ -175,11 +185,13 @@ local function _send_packet(self, req, size)

self.packet_no = self.packet_no + 1

--print("packet no: ", self.packet_no)
-- print("packet no: ", self.packet_no)

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

--print("sending packet...")
-- print("sending packet: ", _dump(packet))

-- print("sending packet... of size " .. #packet)

return sock:send(packet)
end
Expand Down Expand Up @@ -562,9 +574,10 @@ function _M.connect(self, opts)
pos = pos + 9 -- skip filler

-- two lower bytes
self._server_capabilities, pos = _get_byte2(packet, pos)
local capabilities -- server capabilities
capabilities, pos = _get_byte2(packet, pos)

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

self._server_lang = strbyte(packet, pos)
pos = pos + 1
Expand All @@ -578,10 +591,9 @@ function _M.connect(self, opts)
local more_capabilities
more_capabilities, pos = _get_byte2(packet, pos)

self._server_capabilities = bor(self._server_capabilities,
lshift(more_capabilities, 16))
capabilities = bor(capabilities, lshift(more_capabilities, 16))

--print("server capabilities: ", self._server_capabilities)
--print("server capabilities: ", capabilities)

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

local client_flags = 0x3f7cf;

local ssl_verify = opts.ssl_verify
local use_ssl = opts.ssl or ssl_verify

if use_ssl then
if band(capabilities, CLIENT_SSL) == 0 then
return nil, "ssl disabled on server"
end

-- send a SSL Request Packet
local req = _set_byte4(bor(client_flags, CLIENT_SSL))
.. _set_byte4(self._max_packet_size)
.. "\0" -- TODO: add support for charset encoding
.. strrep("\0", 23)

local packet_len = 4 + 4 + 1 + 23
local bytes, err = _send_packet(self, req, packet_len)
if not bytes then
return nil, "failed to send client authentication packet: " .. err
end

local ok, err = sock:sslhandshake(false, nil, ssl_verify)
if not ok then
return nil, "failed to do ssl handshake: " .. (err or "")
end
end

local password = opts.password or ""

local token = _compute_token(password, scramble)

-- local client_flags = self._server_capabilities
local client_flags = 260047;

--print("token: ", _dump(token))

local req = _set_byte4(client_flags)
Expand Down
150 changes: 150 additions & 0 deletions t/ssl.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# vim:set ft= ts=4 sw=4 et:

use Test::Nginx::Socket::Lua;
use Cwd qw(cwd);

repeat_each(2);

plan tests => repeat_each() * (3 * blocks());

my $pwd = cwd();

our $HttpConfig = qq{
resolver \$TEST_NGINX_RESOLVER;
lua_package_path "$pwd/lib/?.lua;$pwd/t/lib/?.lua;;";
lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;";
};

$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
$ENV{TEST_NGINX_MYSQL_PORT} ||= 3306;
$ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1';
$ENV{TEST_NGINX_MYSQL_PATH} ||= '/var/run/mysql/mysql.sock';

#log_level 'warn';

no_long_string();
no_shuffle();
check_accum_error_log();

run_tests();

__DATA__

=== TEST 1: send query w/o result set
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua '
local mysql = require "resty.mysql"
local db = mysql:new()
db:set_timeout(1000) -- 1 sec
local ok, err, errno, sqlstate = db:connect({
host = "$TEST_NGINX_MYSQL_HOST",
port = $TEST_NGINX_MYSQL_PORT,
database = "ngx_test",
user = "ngx_test",
password = "ngx_test",
ssl = true,
})
if not ok then
ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)
return
end
ngx.say("connected to mysql ", db:server_ver(), ".")
local bytes, err = db:send_query("drop table if exists cats")
if not bytes then
ngx.say("failed to send query: ", err)
end
ngx.say("sent ", bytes, " bytes.")
local res, err, errno, sqlstate = db:read_result()
if not res then
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
end
local ljson = require "ljson"
ngx.say("result: ", ljson.encode(res))
local ok, err = db:close()
if not ok then
ngx.say("failed to close: ", err)
return
end
';
}
--- request
GET /t
--- response_body_like chop
^connected to mysql \d\.\S+\.
sent 30 bytes\.
result: \{"affected_rows":0,"insert_id":0,"server_status":2,"warning_count":[01]\}$
--- no_error_log
[error]



=== TEST 2: send query w/o result set (verify)
--- http_config eval: $::HttpConfig
--- config
lua_ssl_trusted_certificate /etc/test.crt; # assuming used by the MySQL server
location /t {
content_by_lua '
local mysql = require "resty.mysql"
local db = mysql:new()
db:set_timeout(1000) -- 1 sec
local ok, err, errno, sqlstate = db:connect({
host = "$TEST_NGINX_MYSQL_HOST",
port = $TEST_NGINX_MYSQL_PORT,
database = "ngx_test",
user = "ngx_test",
password = "ngx_test",
ssl = true,
ssl_verify = true,
})
if not ok then
ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)
return
end
ngx.say("connected to mysql ", db:server_ver(), ".")
local bytes, err = db:send_query("drop table if exists cats")
if not bytes then
ngx.say("failed to send query: ", err)
end
ngx.say("sent ", bytes, " bytes.")
local res, err, errno, sqlstate = db:read_result()
if not res then
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
end
local ljson = require "ljson"
ngx.say("result: ", ljson.encode(res))
local ok, err = db:close()
if not ok then
ngx.say("failed to close: ", err)
return
end
';
}
--- request
GET /t
--- response_body_like chop
^connected to mysql \d\.\S+\.
sent 30 bytes\.
result: \{"affected_rows":0,"insert_id":0,"server_status":2,"warning_count":[01]\}$
--- no_error_log
[error]

0 comments on commit ecc5084

Please sign in to comment.