Skip to content

Commit 0fa6b3d

Browse files
authored
feat: adding custom SNI for etcd TLS requests (#145)
Signed-off-by: tzssangglass <[email protected]>
1 parent 23945e8 commit 0fa6b3d

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed

api_v3.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Method
4545
- `ssl_key_path`: string - path to the client key
4646
- `serializer`: string - serializer type, default `json`, also support `raw` to keep origin string value.
4747
- `extra_headers`: table - adding custom headers for etcd requests.
48+
- `sni`: string - adding custom SNI fot etcd TLS requests.
4849

4950
The client method returns either a `etcd` object or an `error string`.
5051

lib/resty/etcd/v3.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ local function http_request_uri(self, http_cli, method, uri, body, headers, keep
8888
ssl_verify = self.ssl_verify,
8989
ssl_cert_path = self.ssl_cert_path,
9090
ssl_key_path = self.ssl_key_path,
91+
ssl_server_name = self.sni,
9192
})
9293

9394
if err then
@@ -208,6 +209,7 @@ function _M.new(opts)
208209
end
209210
local serializer = opts.serializer
210211
local extra_headers = opts.extra_headers
212+
local sni = opts.sni
211213

212214
if not typeof.uint(timeout) then
213215
return nil, 'opts.timeout must be unsigned integer'
@@ -286,6 +288,7 @@ function _M.new(opts)
286288
ssl_cert_path = opts.ssl_cert_path,
287289
ssl_key_path = opts.ssl_key_path,
288290
extra_headers = extra_headers,
291+
sni = sni,
289292
},
290293
mt)
291294
end
@@ -676,6 +679,7 @@ local function request_chunk(self, method, path, opts, timeout)
676679
body = body,
677680
query = query,
678681
headers = headers,
682+
ssl_server_name = self.sni,
679683
})
680684
utils.log_info("http request method: ", method, " path: ", path,
681685
" body: ", body, " query: ", query)

t/v3/sni.t

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use Test::Nginx::Socket::Lua;
2+
3+
log_level('info');
4+
no_long_string();
5+
repeat_each(1);
6+
7+
my $enable_tls = $ENV{ETCD_ENABLE_MTLS};
8+
if ($enable_tls eq "TRUE") {
9+
plan 'no_plan';
10+
} else {
11+
plan(skip_all => "etcd is not capable for mTLS connection");
12+
}
13+
14+
my $http_config = <<'_EOC_';
15+
lua_socket_log_errors off;
16+
lua_package_path 'lib/?.lua;/usr/share/lua/5.1/?.lua;;';
17+
lua_ssl_trusted_certificate ../../../t/certs/mtls_ca.crt;
18+
init_by_lua_block {
19+
local cjson = require("cjson.safe")
20+
21+
function check_res(data, err, val, status)
22+
if err then
23+
ngx.say("err: ", err)
24+
ngx.exit(200)
25+
end
26+
27+
if val then
28+
if data.body.kvs==nil then
29+
ngx.exit(404)
30+
end
31+
if data.body.kvs and val ~= data.body.kvs[1].value then
32+
ngx.say("failed to check value")
33+
ngx.log(ngx.ERR, "failed to check value, got: ", data.body.kvs[1].value,
34+
", expect: ", val)
35+
ngx.exit(200)
36+
else
37+
ngx.say("checked val as expect: ", val)
38+
end
39+
end
40+
41+
if status and status ~= data.status then
42+
ngx.exit(data.status)
43+
end
44+
end
45+
46+
function new_etcd(ssl_verify, ssl_cert_path, ssl_key_path, sni)
47+
return require "resty.etcd" .new({
48+
protocol = "v3",
49+
api_prefix = "/v3",
50+
http_host = {
51+
"https://127.0.0.1:12379",
52+
"https://127.0.0.1:22379",
53+
"https://127.0.0.1:32379",
54+
},
55+
ssl_verify = ssl_verify,
56+
ssl_cert_path = ssl_cert_path or "t/certs/mtls_client.crt",
57+
ssl_key_path = ssl_key_path or "t/certs/mtls_client.key",
58+
sni = sni,
59+
})
60+
end
61+
}
62+
_EOC_
63+
64+
add_block_preprocessor(sub {
65+
my ($block) = @_;
66+
67+
if (!$block->request) {
68+
$block->set_value("request", "GET /t");
69+
}
70+
71+
if (!$block->http_config) {
72+
$block->set_value("http_config", $http_config);
73+
}
74+
75+
if (!$block->no_error_log && !$block->error_log) {
76+
$block->set_value("no_error_log", "[error]\n[alert]");
77+
}
78+
});
79+
80+
run_tests();
81+
82+
__DATA__
83+
84+
=== TEST 1: without sni, use host(127.0.0.1) as sni by default
85+
--- config
86+
location /t {
87+
content_by_lua_block {
88+
local etcd, err = new_etcd(true)
89+
local res, err = etcd:set("/test", { a='abc'})
90+
if err then
91+
ngx.say(err)
92+
else
93+
ngx.say("done")
94+
end
95+
}
96+
}
97+
--- response_body
98+
certificate host mismatch
99+
100+
101+
102+
=== TEST 2: certificate host mismatch (requesy uri)
103+
--- config
104+
location /t {
105+
content_by_lua_block {
106+
local etcd, err = new_etcd(true, nil, nil, "wrong.sni")
107+
local res, err = etcd:set("/test", { a='abc'})
108+
if err then
109+
ngx.say(err)
110+
else
111+
ngx.say("done")
112+
end
113+
}
114+
}
115+
--- response_body
116+
certificate host mismatch
117+
118+
119+
120+
=== TEST 3: sni match server cert common name (requesy uri)
121+
--- config
122+
location /t {
123+
content_by_lua_block {
124+
local etcd, err = new_etcd(true, nil, nil, "admin.apisix.dev")
125+
local res, err = etcd:set("/test", { a='abc'})
126+
if err then
127+
ngx.say(err)
128+
else
129+
ngx.say("done")
130+
end
131+
}
132+
}
133+
--- response_body
134+
done
135+
136+
137+
138+
=== TEST 4: certificate host mismatch (requesy chunk)
139+
--- config
140+
location /t {
141+
content_by_lua_block {
142+
local etcd, err = new_etcd(true, nil, nil, "127.0.0.1")
143+
local res, err = etcd:set("/test", "abc")
144+
check_res(res, err)
145+
146+
ngx.timer.at(0.1, function ()
147+
etcd:set("/test", "bcd3")
148+
end)
149+
150+
local cur_time = ngx.now()
151+
local body_chunk_fun, err = etcd:watch("/test", {timeout = 0.5})
152+
if not body_chunk_fun then
153+
ngx.say("failed to watch: ", err)
154+
else
155+
ngx.say("done")
156+
end
157+
158+
}
159+
}
160+
--- response_body
161+
err: certificate host mismatch
162+
163+
164+
165+
=== TEST 5: sni match server cert common name (requesy chunk)
166+
--- config
167+
location /t {
168+
content_by_lua_block {
169+
local etcd, err = new_etcd(true, nil, nil, "admin.apisix.dev")
170+
local res, err = etcd:set("/test", "abc")
171+
check_res(res, err)
172+
173+
ngx.timer.at(0.1, function ()
174+
etcd:set("/test", "bcd3")
175+
end)
176+
177+
local cur_time = ngx.now()
178+
local body_chunk_fun, err = etcd:watch("/test", {timeout = 0.5})
179+
if not body_chunk_fun then
180+
ngx.say("failed to watch: ", err)
181+
else
182+
ngx.say("done")
183+
end
184+
185+
}
186+
}
187+
--- response_body
188+
done

0 commit comments

Comments
 (0)