Skip to content

Commit dde58ac

Browse files
authored
feat(grpc): add tls support (#189)
1 parent 8cd8bc2 commit dde58ac

File tree

4 files changed

+268
-2
lines changed

4 files changed

+268
-2
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,4 @@ jobs:
9898
etcd --version
9999
goreman -f ./t/$GOREMAN_CONF start > goreman.log 2>&1 &
100100
sleep 5
101-
prove -I../test-nginx/lib t/v3/mtls.t
101+
prove -I../test-nginx/lib t/v3/mtls.t t/v3/grpc/mtls.t

lib/resty/etcd/v3.lua

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,15 +417,28 @@ function _M.new(opts)
417417
cli.grpc = grpc
418418
cli.call_opts = {}
419419

420+
local connect_opts = {
421+
max_recv_msg_size = 2147483647,
422+
}
423+
420424
local endpoint, err = choose_endpoint(cli)
421425
if not endpoint then
422426
return nil, err
423427
end
424428

429+
if endpoint.scheme == "https" then
430+
connect_opts.insecure = false
431+
end
432+
433+
connect_opts.tls_verify = cli.ssl_verify
434+
connect_opts.client_cert = cli.ssl_cert_path
435+
connect_opts.client_key = cli.ssl_key_path
436+
connect_opts.trusted_ca = opts.trusted_ca
437+
425438
-- TODO: implement proxing via unix socket once we have support sync conf via gRPC
426439
-- TODO: we don't support IPv6 yet, should the user pass `[host]:port` so we don't need
427440
-- to adapt it?
428-
local conn, err = grpc.connect(endpoint.address .. ":" .. endpoint.port)
441+
local conn, err = grpc.connect(endpoint.address .. ":" .. endpoint.port, connect_opts)
429442
if not conn then
430443
return nil, err
431444
end

t/v3/grpc/mtls.t

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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 (defined($enable_tls) && $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 $main_config = <<'_EOC_';
15+
thread_pool grpc-client-nginx-module threads=1;
16+
_EOC_
17+
18+
my $http_config = <<'_EOC_';
19+
lua_socket_log_errors off;
20+
lua_package_path 'lib/?.lua;/usr/share/lua/5.1/?.lua;;';
21+
init_by_lua_block {
22+
local cjson = require("cjson.safe")
23+
24+
function check_res(data, err, val, status)
25+
if err then
26+
ngx.say("err: ", err)
27+
ngx.exit(200)
28+
end
29+
30+
if val then
31+
if data.body.kvs==nil then
32+
ngx.exit(404)
33+
end
34+
if data.body.kvs and val ~= data.body.kvs[1].value then
35+
ngx.say("failed to check value")
36+
ngx.log(ngx.ERR, "failed to check value, got: ", data.body.kvs[1].value,
37+
", expect: ", val)
38+
ngx.exit(200)
39+
else
40+
ngx.say("checked val as expect: ", val)
41+
end
42+
end
43+
44+
if status and status ~= data.status then
45+
ngx.exit(data.status)
46+
end
47+
end
48+
49+
function new_etcd(ssl_verify, ssl_cert_path, ssl_key_path, trusted_ca)
50+
return require "resty.etcd" .new({
51+
protocol = "v3",
52+
api_prefix = "/v3",
53+
http_host = {
54+
"https://127.0.0.1:12379",
55+
},
56+
use_grpc = true,
57+
ssl_verify = ssl_verify,
58+
ssl_cert_path = ssl_cert_path or "t/certs/mtls_client.crt",
59+
ssl_key_path = ssl_key_path or "t/certs/mtls_client.key",
60+
trusted_ca = trusted_ca,
61+
})
62+
end
63+
}
64+
_EOC_
65+
66+
add_block_preprocessor(sub {
67+
my ($block) = @_;
68+
69+
if (!$block->request) {
70+
$block->set_value("request", "GET /t");
71+
}
72+
73+
if (!$block->http_config) {
74+
$block->set_value("http_config", $http_config);
75+
}
76+
77+
if (!$block->main_config) {
78+
$block->set_value("main_config", $main_config);
79+
}
80+
81+
if (!$block->no_error_log && !$block->error_log) {
82+
$block->set_value("no_error_log", "[error]\n[alert]");
83+
}
84+
});
85+
86+
run_tests();
87+
88+
__DATA__
89+
90+
=== TEST 1: TLS no verify
91+
--- config
92+
location /t {
93+
content_by_lua_block {
94+
local etcd, err = new_etcd(false)
95+
check_res(etcd, err)
96+
97+
local res, err = etcd:set("/test", { a='abc'})
98+
check_res(res, err)
99+
ngx.say("done")
100+
}
101+
}
102+
--- response_body
103+
done
104+
105+
106+
107+
=== TEST 2: TLS verify
108+
--- config
109+
location /t {
110+
content_by_lua_block {
111+
local etcd, err = new_etcd(true)
112+
check_res(etcd, err)
113+
114+
local res, err = etcd:set("/test", { a='abc'})
115+
check_res(res, err)
116+
ngx.say("done")
117+
}
118+
}
119+
--- response_body_like eval
120+
qr/cannot validate certificate/
121+
122+
123+
124+
=== TEST 3: bad client certificate
125+
--- config
126+
location /t {
127+
content_by_lua_block {
128+
local etcd, err = new_etcd(false, "t/certs/etcd.pem", "t/certs/etcd.key")
129+
check_res(etcd, err)
130+
131+
local res, err = etcd:set("/test", { a='abc'})
132+
check_res(res, err)
133+
ngx.say("done")
134+
}
135+
}
136+
--- response_body_like eval
137+
qr/bad certificate/

t/v3/grpc/tls.t

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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_TLS};
8+
if (defined($enable_tls) && $enable_tls eq "TRUE") {
9+
plan 'no_plan';
10+
} else {
11+
plan(skip_all => "etcd is not capable for TLS connection");
12+
}
13+
14+
our $HttpConfig = <<'_EOC_';
15+
lua_socket_log_errors off;
16+
lua_package_path 'lib/?.lua;/usr/local/share/lua/5.3/?.lua;/usr/share/lua/5.1/?.lua;;';
17+
init_by_lua_block {
18+
local cjson = require("cjson.safe")
19+
20+
function check_res(data, err, val, status)
21+
if err then
22+
ngx.say("err: ", err)
23+
ngx.exit(200)
24+
end
25+
26+
if val then
27+
if data.body.kvs==nil then
28+
ngx.exit(404)
29+
end
30+
if data.body.kvs and val ~= data.body.kvs[1].value then
31+
ngx.say("failed to check value")
32+
ngx.log(ngx.ERR, "failed to check value, got: ", data.body.kvs[1].value,
33+
", expect: ", val)
34+
ngx.exit(200)
35+
else
36+
ngx.say("checked val as expect: ", val)
37+
end
38+
end
39+
40+
if status and status ~= data.status then
41+
ngx.exit(data.status)
42+
end
43+
end
44+
}
45+
_EOC_
46+
47+
add_block_preprocessor(sub {
48+
my ($block) = @_;
49+
50+
if (!$block->main_config) {
51+
$block->set_value("main_config", "thread_pool grpc-client-nginx-module threads=1;");
52+
}
53+
54+
if (!$block->request) {
55+
$block->set_value("request", "GET /t");
56+
}
57+
58+
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
59+
$block->set_value("no_error_log", "[error]");
60+
}
61+
});
62+
63+
run_tests();
64+
65+
__DATA__
66+
67+
=== TEST 1: TLS no verify
68+
--- http_config eval: $::HttpConfig
69+
--- config
70+
location /t {
71+
content_by_lua_block {
72+
local etcd, err = require "resty.etcd" .new({
73+
protocol = "v3",
74+
http_host = {
75+
"https://127.0.0.1:12379",
76+
"https://127.0.0.1:22379",
77+
"https://127.0.0.1:32379",
78+
},
79+
ssl_verify = false,
80+
use_grpc = true,
81+
})
82+
check_res(etcd, err)
83+
84+
local res, err = etcd:set("/test", { a='abc'})
85+
check_res(res, err)
86+
ngx.say("done")
87+
}
88+
}
89+
--- response_body
90+
done
91+
92+
93+
94+
=== TEST 2: TLS verify
95+
--- http_config eval: $::HttpConfig
96+
--- config
97+
location /t {
98+
content_by_lua_block {
99+
local etcd, err = require "resty.etcd" .new({
100+
protocol = "v3",
101+
http_host = {
102+
"https://127.0.0.1:12379",
103+
"https://127.0.0.1:22379",
104+
"https://127.0.0.1:32379",
105+
},
106+
use_grpc = true,
107+
})
108+
check_res(etcd, err)
109+
110+
local res, err = etcd:set("/test", { a='abc'})
111+
check_res(res, err)
112+
ngx.say("done")
113+
}
114+
}
115+
--- response_body eval
116+
qr/authentication handshake failed/

0 commit comments

Comments
 (0)