Skip to content

Commit 9660691

Browse files
authored
feature: support TLS connection with etcd. (#86)
* test: call add-auth script only if ETCD_ENABLE_TLS is undefined
1 parent 0533e60 commit 9660691

File tree

10 files changed

+285
-9
lines changed

10 files changed

+285
-9
lines changed

.travis.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ matrix:
2525
- env: ETCD_VER=3.2.0 GOREMAN_CONF=Procfile-single
2626
- env: ETCD_VER=3.3.0 GOREMAN_CONF=Procfile-single-enable-v2
2727
- env: ETCD_VER=3.4.0 GOREMAN_CONF=Procfile-single-enable-v2
28+
- env: ETCD_VER=3.4.0 GOREMAN_CONF=Procfile-single-enable-tls ETCD_ENABLE_TLS=TRUE
2829

2930
env:
3031
global:
@@ -59,8 +60,8 @@ script:
5960
- sleep 5
6061
- chmod +x ./t/v2/add-auth.sh
6162
- chmod +x ./t/v3/add-auth.sh
62-
- ./t/v2/add-auth.sh
63-
- ./t/v3/add-auth.sh
63+
- [ $ETCD_ENABLE_TLS != TRUE ] && ./t/v2/add-auth.sh
64+
- [ $ETCD_ENABLE_TLS != TRUE ] && ./t/v3/add-auth.sh
6465
- cat goreman.log
6566
- ps -ef | grep etcd
6667
- luajit -v

api_v2.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Method
3636
- `timeout`: int
3737
request timeout seconds.
3838
- `serializer`: string - serializer type, default `json`, also support `raw` to keep origin string value.
39+
- `ssl_verify`: boolean - whether to verify the etcd certificate when originating TLS connection with etcd (if you want to communicate to etcd with TLS connection, use `https` scheme in your `http_host`).
3940

4041
The client methods returns either a `HTTP Response Entity` or an `error string`.
4142

api_v3.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Method
4040
- `api_prefix`: string
4141
to suit [etcd v3 api gateway](https://github.com/etcd-io/etcd/blob/master/Documentation/dev-guide/api_grpc_gateway.md#notes).
4242
it will autofill by fetching etcd version if this option empty.
43+
- `ssl_verify`: boolean - whether to verify the etcd certificate when originating TLS connection with etcd (if you want to communicate to etcd with TLS connection, use `https` scheme in your `http_host`).
4344

4445
The client methods returns either a `etcd` object or an `error string`.
4546

lib/resty/etcd/v2.lua

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function _M.new(opts)
4747
local user = opts.user
4848
local password = opts.password
4949
local serializer = opts.serializer
50+
local ssl_verify = opts.ssl_verify
5051

5152
if not typeof.uint(timeout) then
5253
return nil, 'opts.timeout must be unsigned integer'
@@ -106,10 +107,10 @@ function _M.new(opts)
106107
user = user,
107108
password = password,
108109
endpoints = endpoints,
109-
serializer = serializer
110+
serializer = serializer,
111+
ssl_verify = ssl_verify,
110112
},
111113
mt)
112-
113114
end
114115

115116
local content_type = {
@@ -175,6 +176,7 @@ local function _request(self, method, uri, opts, timeout)
175176
method = method,
176177
body = body,
177178
headers = headers,
179+
ssl_verify = self.ssl_verify,
178180
})
179181

180182
if err then

lib/resty/etcd/v3.lua

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ local function _request_uri(self, method, uri, opts, timeout, ignore_auth)
6969
body = body,
7070
headers = headers,
7171
keepalive = keepalive,
72+
ssl_verify = self.ssl_verify,
7273
})
7374

7475
if err then
@@ -104,8 +105,9 @@ function _M.new(opts)
104105
local api_prefix = opts.api_prefix
105106
local key_prefix = opts.key_prefix or ""
106107
local http_host = opts.http_host
107-
local user = opts.user
108-
local password = opts.password
108+
local user = opts.user
109+
local password = opts.password
110+
local ssl_verify = opts.ssl_verfiy
109111

110112
if not typeof.uint(timeout) then
111113
return nil, 'opts.timeout must be unsigned integer'
@@ -160,15 +162,16 @@ function _M.new(opts)
160162

161163
return setmetatable({
162164
last_auth_time = now(), -- save last Authentication time
163-
jwt_token = nil, -- last Authentication token
164-
is_auth = not not (user and password),
165+
jwt_token = nil, -- last Authentication token
166+
is_auth = not not (user and password),
165167
user = user,
166168
password = password,
167169
timeout = timeout,
168170
ttl = ttl,
169171
is_cluster = #endpoints > 1,
170172
endpoints = endpoints,
171-
key_prefix = key_prefix,
173+
key_prefix = key_prefix,
174+
ssl_verify = ssl_verify,
172175
},
173176
mt)
174177
end

t/Procfile-single-enable-tls

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Use goreman to run `go get github.com/mattn/goreman`
2+
etcd0: etcd
3+
etcd1: etcd --name infra1 --listen-client-urls https://127.0.0.1:12379 --advertise-client-urls https://127.0.0.1:12379 --listen-peer-urls http://127.0.0.1:12380 --initial-advertise-peer-urls http://127.0.0.1:12380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --cert-file ./t/certs/etcd.pem --key-file ./t/certs/etcd.key
4+
etcd2: etcd --name infra2 --listen-client-urls https://127.0.0.1:22379 --advertise-client-urls https://127.0.0.1:22379 --listen-peer-urls http://127.0.0.1:22380 --initial-advertise-peer-urls http://127.0.0.1:22380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --cert-file ./t/certs/etcd.pem --key-file ./t/certs/etcd.key
5+
etcd3: etcd --name infra3 --listen-client-urls https://127.0.0.1:32379 --advertise-client-urls https://127.0.0.1:32379 --listen-peer-urls http://127.0.0.1:32380 --initial-advertise-peer-urls http://127.0.0.1:32380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --cert-file ./t/certs/etcd.pem --key-file ./t/certs/etcd.key
6+
# A learner node can be started using Procfile.learner

t/certs/etcd.key

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCf6sMQke4OUrPf
3+
lZXqKZC4uaQLMxJB2DgacBf4Q3kXcVNCNHtc2U3U5cMD8tr3/Jt5MKKun5jQrbXV
4+
FF4eVr4Iv9jgPBwQc2kBUC9QL/alsMhEuXMeqdGQcCK3F0CLJdV3zUlKUDU0kg+O
5+
Exnbl1CHXrIbpD7zLy1i3s8p39v1pYFYf4WlrQxvfa/xo97gXY5dJv8RryryLzRc
6+
uhHYBvX5MHCGpbrY61JxpfZqBo8CmLuHl1tmbeXpdHdQB11LKiuL6HtKflNjc6rg
7+
5r8bXl1nZbM/KOZEE+muA1LVoaTyHzY/aGXz0bNy4QRUO+De9JFcTDgnXnNZVG5x
8+
cyyDBpc9AgMBAAECggEAatcEtehZPJaCeClPPF/Cwbe9YoIfe4BCk186lHI3z7K1
9+
5nB7zt+bwVY0AUpagv3wvXoB5lrYVOsJpa9y5iAb3GqYMc/XDCKfD/KLea5hwfcn
10+
BctEn0LjsPVKLDrLs2t2gBDWG2EU+udunwQh7XTdp2Nb6V3FdOGbGAg2LgrSwP1g
11+
0r4z14F70oWGYyTQ5N8UGuyryVrzQH525OYl38Yt7R6zJ/44FVi/2TvdfHM5ss39
12+
SXWi00Q30fzaBEf4AdHVwVCRKctwSbrIOyM53kiScFDmBGRblCWOxXbiFV+d3bjX
13+
gf2zxs7QYZrFOzOO7kLtHGua4itEB02497v+1oKDwQKBgQDOBvCVGRe2WpItOLnj
14+
SF8iz7Sm+jJGQz0D9FhWyGPvrN7IXGrsXavA1kKRz22dsU8xdKk0yciOB13Wb5y6
15+
yLsr/fPBjAhPb4h543VHFjpAQcxpsH51DE0b2oYOWMmz+rXGB5Jy8EkP7Q4njIsc
16+
2wLod1dps8OT8zFx1jX3Us6iUQKBgQDGtKkfsvWi3HkwjFTR+/Y0oMz7bSruE5Z8
17+
g0VOHPkSr4XiYgLpQxjbNjq8fwsa/jTt1B57+By4xLpZYD0BTFuf5po+igSZhH8s
18+
QS5XnUnbM7d6Xr/da7ZkhSmUbEaMeHONSIVpYNgtRo4bB9Mh0l1HWdoevw/w5Ryt
19+
L/OQiPhfLQKBgQCh1iG1fPh7bbnVe/HI71iL58xoPbCwMLEFIjMiOFcINirqCG6V
20+
LR91Ytj34JCihl1G4/TmWnsH1hGIGDRtJLCiZeHL70u32kzCMkI1jOhFAWqoutMa
21+
7obDkmwraONIVW/kFp6bWtSJhhTQTD4adI9cPCKWDXdcCHSWj0Xk+U8HgQKBgBng
22+
t1HYhaLzIZlP/U/nh3XtJyTrX7bnuCZ5FhKJNWrYjxAfgY+NXHRYCKg5x2F5j70V
23+
be7pLhxmCnrPTMKZhik56AaTBOxVVBaYWoewhUjV4GRAaK5Wc8d9jB+3RizPFwVk
24+
V3OU2DJ1SNZ+W2HBOsKrEfwFF/dgby6i2w6MuAP1AoGBAIxvxUygeT/6P0fHN22P
25+
zAHFI4v2925wYdb7H//D8DIADyBwv18N6YH8uH7L+USZN7e4p2k8MGGyvTXeC6aX
26+
IeVtU6fH57Ddn59VPbF20m8RCSkmBvSdcbyBmqlZSBE+fKwCliKl6u/GH0BNAWKz
27+
r8yiEiskqRmy7P7MY9hDmEbG
28+
-----END PRIVATE KEY-----

t/certs/etcd.pem

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDrzCCApegAwIBAgIJAI3Meu/gJVTLMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV
3+
BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL
4+
BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl
5+
ci5sb2NhbDAeFw0yMDEwMjgwMzMzMDJaFw0yMTEwMjgwMzMzMDJaMG4xCzAJBgNV
6+
BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL
7+
BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl
8+
ci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/qwxCR7g5S
9+
s9+VleopkLi5pAszEkHYOBpwF/hDeRdxU0I0e1zZTdTlwwPy2vf8m3kwoq6fmNCt
10+
tdUUXh5Wvgi/2OA8HBBzaQFQL1Av9qWwyES5cx6p0ZBwIrcXQIsl1XfNSUpQNTSS
11+
D44TGduXUIdeshukPvMvLWLezynf2/WlgVh/haWtDG99r/Gj3uBdjl0m/xGvKvIv
12+
NFy6EdgG9fkwcIalutjrUnGl9moGjwKYu4eXW2Zt5el0d1AHXUsqK4voe0p+U2Nz
13+
quDmvxteXWdlsz8o5kQT6a4DUtWhpPIfNj9oZfPRs3LhBFQ74N70kVxMOCdec1lU
14+
bnFzLIMGlz0CAwEAAaNQME4wHQYDVR0OBBYEFFHeljijrr+SPxlH5fjHRPcC7bv2
15+
MB8GA1UdIwQYMBaAFFHeljijrr+SPxlH5fjHRPcC7bv2MAwGA1UdEwQFMAMBAf8w
16+
DQYJKoZIhvcNAQELBQADggEBAG6NNTK7sl9nJxeewVuogCdMtkcdnx9onGtCOeiQ
17+
qvh5Xwn9akZtoLMVEdceU0ihO4wILlcom3OqHs9WOd6VbgW5a19Thh2toxKidHz5
18+
rAaBMyZsQbFb6+vFshZwoCtOLZI/eIZfUUMFqMXlEPrKru1nSddNdai2+zi5rEnM
19+
HCot43+3XYuqkvWlOjoi9cP+C4epFYrxpykVbcrtbd7TK+wZNiK3xtDPnVzjdNWL
20+
geAEl9xrrk0ss4nO/EreTQgS46gVU+tLC+b23m2dU7dcKZ7RDoiA9bdVc4a2IsaS
21+
2MvLL4NZ2nUh8hAEHiLtGMAV3C6xNbEyM07hEpDW6vk6tqk=
22+
-----END CERTIFICATE-----

t/v2/tls.t

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 ($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+
run_tests();
48+
49+
__DATA__
50+
51+
=== TEST 1: TLS no verify
52+
--- http_config eval: $::HttpConfig
53+
--- config
54+
location /t {
55+
content_by_lua_block {
56+
local etcd, err = require "resty.etcd" .new({
57+
protocol = "v3",
58+
http_host = {
59+
"https://127.0.0.1:12379",
60+
"https://127.0.0.1:22379",
61+
"https://127.0.0.1:32379",
62+
},
63+
ssl_verify = false,
64+
})
65+
check_res(etcd, err)
66+
67+
local res, err = etcd:set("/test", { a='abc'})
68+
check_res(res, err)
69+
ngx.say("done")
70+
}
71+
}
72+
--- request
73+
GET /t
74+
--- no_error_log
75+
[error]
76+
--- response_body
77+
done
78+
79+
80+
81+
=== TEST 2: TLS verify
82+
--- http_config eval: $::HttpConfig
83+
--- config
84+
location /t {
85+
content_by_lua_block {
86+
local etcd, err = require "resty.etcd" .new({
87+
protocol = "v3",
88+
http_host = {
89+
"https://127.0.0.1:12379",
90+
"https://127.0.0.1:22379",
91+
"https://127.0.0.1:32379",
92+
},
93+
})
94+
check_res(etcd, err)
95+
96+
local res, err = etcd:set("/test", { a='abc'})
97+
check_res(res, err)
98+
ngx.say("done")
99+
}
100+
}
101+
--- request
102+
GET /t
103+
--- no_error_log
104+
[error]
105+
--- response_body
106+
err: 18: self signed certificate

t/v3/tls.t

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 ($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+
run_tests();
48+
49+
__DATA__
50+
51+
=== TEST 1: TLS no verify
52+
--- http_config eval: $::HttpConfig
53+
--- config
54+
location /t {
55+
content_by_lua_block {
56+
local etcd, err = require "resty.etcd" .new({
57+
protocol = "v3",
58+
http_host = {
59+
"https://127.0.0.1:12379",
60+
"https://127.0.0.1:22379",
61+
"https://127.0.0.1:32379",
62+
},
63+
ssl_verify = false,
64+
})
65+
check_res(etcd, err)
66+
67+
local res, err = etcd:set("/test", { a='abc'})
68+
check_res(res, err)
69+
ngx.say("done")
70+
}
71+
}
72+
--- request
73+
GET /t
74+
--- no_error_log
75+
[error]
76+
--- response_body
77+
done
78+
79+
80+
81+
=== TEST 2: TLS verify
82+
--- http_config eval: $::HttpConfig
83+
--- config
84+
location /t {
85+
content_by_lua_block {
86+
local etcd, err = require "resty.etcd" .new({
87+
protocol = "v3",
88+
http_host = {
89+
"https://127.0.0.1:12379",
90+
"https://127.0.0.1:22379",
91+
"https://127.0.0.1:32379",
92+
},
93+
})
94+
check_res(etcd, err)
95+
96+
local res, err = etcd:set("/test", { a='abc'})
97+
check_res(res, err)
98+
ngx.say("done")
99+
}
100+
}
101+
--- request
102+
GET /t
103+
--- no_error_log
104+
[error]
105+
--- response_body
106+
err: 18: self signed certificate

0 commit comments

Comments
 (0)