Skip to content

feature: support TLS connection with etcd. #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ matrix:
- env: ETCD_VER=3.2.0 GOREMAN_CONF=Procfile-single
- env: ETCD_VER=3.3.0 GOREMAN_CONF=Procfile-single-enable-v2
- env: ETCD_VER=3.4.0 GOREMAN_CONF=Procfile-single-enable-v2
- env: ETCD_VER=3.4.0 GOREMAN_CONF=Procfile-single-enable-tls ETCD_ENABLE_TLS=TRUE

env:
global:
Expand Down Expand Up @@ -59,8 +60,8 @@ script:
- sleep 5
- chmod +x ./t/v2/add-auth.sh
- chmod +x ./t/v3/add-auth.sh
- ./t/v2/add-auth.sh
- ./t/v3/add-auth.sh
- [ $ETCD_ENABLE_TLS != TRUE ] && ./t/v2/add-auth.sh
- [ $ETCD_ENABLE_TLS != TRUE ] && ./t/v3/add-auth.sh
- cat goreman.log
- ps -ef | grep etcd
- luajit -v
Expand Down
1 change: 1 addition & 0 deletions api_v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Method
- `timeout`: int
request timeout seconds.
- `serializer`: string - serializer type, default `json`, also support `raw` to keep origin string value.
- `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`).

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

Expand Down
1 change: 1 addition & 0 deletions api_v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Method
- `api_prefix`: string
to suit [etcd v3 api gateway](https://github.com/etcd-io/etcd/blob/master/Documentation/dev-guide/api_grpc_gateway.md#notes).
it will autofill by fetching etcd version if this option empty.
- `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`).

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

Expand Down
6 changes: 4 additions & 2 deletions lib/resty/etcd/v2.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ function _M.new(opts)
local user = opts.user
local password = opts.password
local serializer = opts.serializer
local ssl_verify = opts.ssl_verify

if not typeof.uint(timeout) then
return nil, 'opts.timeout must be unsigned integer'
Expand Down Expand Up @@ -106,10 +107,10 @@ function _M.new(opts)
user = user,
password = password,
endpoints = endpoints,
serializer = serializer
serializer = serializer,
ssl_verify = ssl_verify,
},
mt)

end

local content_type = {
Expand Down Expand Up @@ -175,6 +176,7 @@ local function _request(self, method, uri, opts, timeout)
method = method,
body = body,
headers = headers,
ssl_verify = self.ssl_verify,
})

if err then
Expand Down
13 changes: 8 additions & 5 deletions lib/resty/etcd/v3.lua
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ local function _request_uri(self, method, uri, opts, timeout, ignore_auth)
body = body,
headers = headers,
keepalive = keepalive,
ssl_verify = self.ssl_verify,
})

if err then
Expand Down Expand Up @@ -104,8 +105,9 @@ function _M.new(opts)
local api_prefix = opts.api_prefix
local key_prefix = opts.key_prefix or ""
local http_host = opts.http_host
local user = opts.user
local password = opts.password
local user = opts.user
local password = opts.password
local ssl_verify = opts.ssl_verfiy

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

return setmetatable({
last_auth_time = now(), -- save last Authentication time
jwt_token = nil, -- last Authentication token
is_auth = not not (user and password),
jwt_token = nil, -- last Authentication token
is_auth = not not (user and password),
user = user,
password = password,
timeout = timeout,
ttl = ttl,
is_cluster = #endpoints > 1,
endpoints = endpoints,
key_prefix = key_prefix,
key_prefix = key_prefix,
ssl_verify = ssl_verify,
},
mt)
end
Expand Down
6 changes: 6 additions & 0 deletions t/Procfile-single-enable-tls
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Use goreman to run `go get github.com/mattn/goreman`
etcd0: etcd
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
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
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
# A learner node can be started using Procfile.learner
28 changes: 28 additions & 0 deletions t/certs/etcd.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCf6sMQke4OUrPf
lZXqKZC4uaQLMxJB2DgacBf4Q3kXcVNCNHtc2U3U5cMD8tr3/Jt5MKKun5jQrbXV
FF4eVr4Iv9jgPBwQc2kBUC9QL/alsMhEuXMeqdGQcCK3F0CLJdV3zUlKUDU0kg+O
Exnbl1CHXrIbpD7zLy1i3s8p39v1pYFYf4WlrQxvfa/xo97gXY5dJv8RryryLzRc
uhHYBvX5MHCGpbrY61JxpfZqBo8CmLuHl1tmbeXpdHdQB11LKiuL6HtKflNjc6rg
5r8bXl1nZbM/KOZEE+muA1LVoaTyHzY/aGXz0bNy4QRUO+De9JFcTDgnXnNZVG5x
cyyDBpc9AgMBAAECggEAatcEtehZPJaCeClPPF/Cwbe9YoIfe4BCk186lHI3z7K1
5nB7zt+bwVY0AUpagv3wvXoB5lrYVOsJpa9y5iAb3GqYMc/XDCKfD/KLea5hwfcn
BctEn0LjsPVKLDrLs2t2gBDWG2EU+udunwQh7XTdp2Nb6V3FdOGbGAg2LgrSwP1g
0r4z14F70oWGYyTQ5N8UGuyryVrzQH525OYl38Yt7R6zJ/44FVi/2TvdfHM5ss39
SXWi00Q30fzaBEf4AdHVwVCRKctwSbrIOyM53kiScFDmBGRblCWOxXbiFV+d3bjX
gf2zxs7QYZrFOzOO7kLtHGua4itEB02497v+1oKDwQKBgQDOBvCVGRe2WpItOLnj
SF8iz7Sm+jJGQz0D9FhWyGPvrN7IXGrsXavA1kKRz22dsU8xdKk0yciOB13Wb5y6
yLsr/fPBjAhPb4h543VHFjpAQcxpsH51DE0b2oYOWMmz+rXGB5Jy8EkP7Q4njIsc
2wLod1dps8OT8zFx1jX3Us6iUQKBgQDGtKkfsvWi3HkwjFTR+/Y0oMz7bSruE5Z8
g0VOHPkSr4XiYgLpQxjbNjq8fwsa/jTt1B57+By4xLpZYD0BTFuf5po+igSZhH8s
QS5XnUnbM7d6Xr/da7ZkhSmUbEaMeHONSIVpYNgtRo4bB9Mh0l1HWdoevw/w5Ryt
L/OQiPhfLQKBgQCh1iG1fPh7bbnVe/HI71iL58xoPbCwMLEFIjMiOFcINirqCG6V
LR91Ytj34JCihl1G4/TmWnsH1hGIGDRtJLCiZeHL70u32kzCMkI1jOhFAWqoutMa
7obDkmwraONIVW/kFp6bWtSJhhTQTD4adI9cPCKWDXdcCHSWj0Xk+U8HgQKBgBng
t1HYhaLzIZlP/U/nh3XtJyTrX7bnuCZ5FhKJNWrYjxAfgY+NXHRYCKg5x2F5j70V
be7pLhxmCnrPTMKZhik56AaTBOxVVBaYWoewhUjV4GRAaK5Wc8d9jB+3RizPFwVk
V3OU2DJ1SNZ+W2HBOsKrEfwFF/dgby6i2w6MuAP1AoGBAIxvxUygeT/6P0fHN22P
zAHFI4v2925wYdb7H//D8DIADyBwv18N6YH8uH7L+USZN7e4p2k8MGGyvTXeC6aX
IeVtU6fH57Ddn59VPbF20m8RCSkmBvSdcbyBmqlZSBE+fKwCliKl6u/GH0BNAWKz
r8yiEiskqRmy7P7MY9hDmEbG
-----END PRIVATE KEY-----
22 changes: 22 additions & 0 deletions t/certs/etcd.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIJAI3Meu/gJVTLMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV
BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL
BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl
ci5sb2NhbDAeFw0yMDEwMjgwMzMzMDJaFw0yMTEwMjgwMzMzMDJaMG4xCzAJBgNV
BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL
BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl
ci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/qwxCR7g5S
s9+VleopkLi5pAszEkHYOBpwF/hDeRdxU0I0e1zZTdTlwwPy2vf8m3kwoq6fmNCt
tdUUXh5Wvgi/2OA8HBBzaQFQL1Av9qWwyES5cx6p0ZBwIrcXQIsl1XfNSUpQNTSS
D44TGduXUIdeshukPvMvLWLezynf2/WlgVh/haWtDG99r/Gj3uBdjl0m/xGvKvIv
NFy6EdgG9fkwcIalutjrUnGl9moGjwKYu4eXW2Zt5el0d1AHXUsqK4voe0p+U2Nz
quDmvxteXWdlsz8o5kQT6a4DUtWhpPIfNj9oZfPRs3LhBFQ74N70kVxMOCdec1lU
bnFzLIMGlz0CAwEAAaNQME4wHQYDVR0OBBYEFFHeljijrr+SPxlH5fjHRPcC7bv2
MB8GA1UdIwQYMBaAFFHeljijrr+SPxlH5fjHRPcC7bv2MAwGA1UdEwQFMAMBAf8w
DQYJKoZIhvcNAQELBQADggEBAG6NNTK7sl9nJxeewVuogCdMtkcdnx9onGtCOeiQ
qvh5Xwn9akZtoLMVEdceU0ihO4wILlcom3OqHs9WOd6VbgW5a19Thh2toxKidHz5
rAaBMyZsQbFb6+vFshZwoCtOLZI/eIZfUUMFqMXlEPrKru1nSddNdai2+zi5rEnM
HCot43+3XYuqkvWlOjoi9cP+C4epFYrxpykVbcrtbd7TK+wZNiK3xtDPnVzjdNWL
geAEl9xrrk0ss4nO/EreTQgS46gVU+tLC+b23m2dU7dcKZ7RDoiA9bdVc4a2IsaS
2MvLL4NZ2nUh8hAEHiLtGMAV3C6xNbEyM07hEpDW6vk6tqk=
-----END CERTIFICATE-----
106 changes: 106 additions & 0 deletions t/v2/tls.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use Test::Nginx::Socket::Lua;

log_level('info');
no_long_string();
repeat_each(1);

my $enable_tls = $ENV{ETCD_ENABLE_TLS};
if ($enable_tls eq "TRUE") {
plan 'no_plan';
} else {
plan(skip_all => "etcd is not capable for TLS connection");
}

our $HttpConfig = <<'_EOC_';
lua_socket_log_errors off;
lua_package_path 'lib/?.lua;/usr/local/share/lua/5.3/?.lua;/usr/share/lua/5.1/?.lua;;';
init_by_lua_block {
local cjson = require("cjson.safe")

function check_res(data, err, val, status)
if err then
ngx.say("err: ", err)
ngx.exit(200)
end

if val then
if data.body.kvs==nil then
ngx.exit(404)
end
if data.body.kvs and val ~= data.body.kvs[1].value then
ngx.say("failed to check value")
ngx.log(ngx.ERR, "failed to check value, got: ", data.body.kvs[1].value,
", expect: ", val)
ngx.exit(200)
else
ngx.say("checked val as expect: ", val)
end
end

if status and status ~= data.status then
ngx.exit(data.status)
end
end
}
_EOC_

run_tests();

__DATA__

=== TEST 1: TLS no verify
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua_block {
local etcd, err = require "resty.etcd" .new({
protocol = "v3",
http_host = {
"https://127.0.0.1:12379",
"https://127.0.0.1:22379",
"https://127.0.0.1:32379",
},
ssl_verify = false,
})
check_res(etcd, err)

local res, err = etcd:set("/test", { a='abc'})
check_res(res, err)
ngx.say("done")
}
}
--- request
GET /t
--- no_error_log
[error]
--- response_body
done



=== TEST 2: TLS verify
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua_block {
local etcd, err = require "resty.etcd" .new({
protocol = "v3",
http_host = {
"https://127.0.0.1:12379",
"https://127.0.0.1:22379",
"https://127.0.0.1:32379",
},
})
check_res(etcd, err)

local res, err = etcd:set("/test", { a='abc'})
check_res(res, err)
ngx.say("done")
}
}
--- request
GET /t
--- no_error_log
[error]
--- response_body
err: 18: self signed certificate
106 changes: 106 additions & 0 deletions t/v3/tls.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use Test::Nginx::Socket::Lua;

log_level('info');
no_long_string();
repeat_each(1);

my $enable_tls = $ENV{ETCD_ENABLE_TLS};
if ($enable_tls eq "TRUE") {
plan 'no_plan';
} else {
plan(skip_all => "etcd is not capable for TLS connection");
}

our $HttpConfig = <<'_EOC_';
lua_socket_log_errors off;
lua_package_path 'lib/?.lua;/usr/local/share/lua/5.3/?.lua;/usr/share/lua/5.1/?.lua;;';
init_by_lua_block {
local cjson = require("cjson.safe")

function check_res(data, err, val, status)
if err then
ngx.say("err: ", err)
ngx.exit(200)
end

if val then
if data.body.kvs==nil then
ngx.exit(404)
end
if data.body.kvs and val ~= data.body.kvs[1].value then
ngx.say("failed to check value")
ngx.log(ngx.ERR, "failed to check value, got: ", data.body.kvs[1].value,
", expect: ", val)
ngx.exit(200)
else
ngx.say("checked val as expect: ", val)
end
end

if status and status ~= data.status then
ngx.exit(data.status)
end
end
}
_EOC_

run_tests();

__DATA__

=== TEST 1: TLS no verify
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua_block {
local etcd, err = require "resty.etcd" .new({
protocol = "v3",
http_host = {
"https://127.0.0.1:12379",
"https://127.0.0.1:22379",
"https://127.0.0.1:32379",
},
ssl_verify = false,
})
check_res(etcd, err)

local res, err = etcd:set("/test", { a='abc'})
check_res(res, err)
ngx.say("done")
}
}
--- request
GET /t
--- no_error_log
[error]
--- response_body
done



=== TEST 2: TLS verify
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua_block {
local etcd, err = require "resty.etcd" .new({
protocol = "v3",
http_host = {
"https://127.0.0.1:12379",
"https://127.0.0.1:22379",
"https://127.0.0.1:32379",
},
})
check_res(etcd, err)

local res, err = etcd:set("/test", { a='abc'})
check_res(res, err)
ngx.say("done")
}
}
--- request
GET /t
--- no_error_log
[error]
--- response_body
err: 18: self signed certificate