Skip to content

Commit 7f95abf

Browse files
DoubleSpoutmembphis
authored andcommitted
feature: add auth v3 (#41)
bugfix: 1. fix watchdir delete not work 2. fix readdir kv.key not decode_base64 change: check if kv.value is exist feature: add v3 auth
1 parent 207db15 commit 7f95abf

File tree

7 files changed

+253
-24
lines changed

7 files changed

+253
-24
lines changed

.travis.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ matrix:
2929
env:
3030
global:
3131
- OPENRESTY_PREFIX=/usr/local/openresty
32+
- AUTH_ENDPOINT_V2=http://127.0.0.1:12379
33+
- AUTH_ENDPOINT_V3=127.0.0.1:12379
34+
- AUTH_USER=root
35+
- AUTH_PWD=abc123
3236

3337
install:
3438
- git clone https://github.com/openresty/test-nginx.git test-nginx
@@ -50,6 +54,10 @@ script:
5054
- etcd --version
5155
- goreman -f ./t/$GOREMAN_CONF start > goreman.log 2>&1 &
5256
- sleep 5
57+
- chmod +x ./t/v2/add-auth.sh
58+
- chmod +x ./t/v3/add-auth.sh
59+
- ./t/v2/add-auth.sh
60+
- ./t/v3/add-auth.sh
5361
- cat goreman.log
5462
- ps -ef | grep etcd
5563
- make test

lib/resty/etcd.lua

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,19 @@ function _M.new(opts)
4646
.. ":" .. (opts.port or 2379)
4747
opts.ttl = opts.ttl or -1
4848

49-
local ver, err = etcd_version(opts)
50-
if not ver then
51-
return nil, err
52-
end
53-
5449
local protocol = opts and opts.protocol or "v2"
5550
if protocol == "v3" then
56-
local sub_ver = ver.etcdserver:sub(1, 4)
57-
local etcd_prefix = prefix_v3[sub_ver] or "/v3beta"
51+
52+
local etcd_prefix = opts.etcd_prefix
53+
-- if opts special the etcd_prefix,no need to check version
54+
if not etcd_prefix then
55+
local ver, err = etcd_version(opts)
56+
if not ver then
57+
return nil, err
58+
end
59+
local sub_ver = ver.etcdserver:sub(1, 4)
60+
etcd_prefix = prefix_v3[sub_ver] or "/v3beta"
61+
end
5862
opts.api_prefix = etcd_prefix .. (opts.api_prefix or "")
5963
return etcdv3.new(opts)
6064
end

lib/resty/etcd/v3.lua

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local clear_tab = require("table.clear")
66
local utils = require("resty.etcd.utils")
77
local tab_nkeys = require("table.nkeys")
88
local encode_args = ngx.encode_args
9+
local now = ngx.now
910
local sub_str = string.sub
1011
local str_byte = string.byte
1112
local str_char = string.char
@@ -22,11 +23,13 @@ local INIT_COUNT_RESIZE = 2e8
2223

2324
local _M = {}
2425

25-
2626
local mt = { __index = _M }
2727

28+
-- define local refresh function variable
29+
local refresh_jwt_token
30+
31+
local function _request_uri(self, method, uri, opts, timeout, ignore_auth)
2832

29-
local function _request_uri(self, method, uri, opts, timeout)
3033
local body
3134
if opts and opts.body and tab_nkeys(opts.body) > 0 then
3235
body = encode_json(opts.body) --encode_args(opts.body)
@@ -36,6 +39,21 @@ local function _request_uri(self, method, uri, opts, timeout)
3639
uri = uri .. '?' .. encode_args(opts.query)
3740
end
3841

42+
local headers = {}
43+
local keepalive = true
44+
if self.is_auth then
45+
if not ignore_auth then
46+
-- authentication reqeust not need auth request
47+
local _, err = refresh_jwt_token(self)
48+
if err then
49+
return nil, err
50+
end
51+
else
52+
keepalive = false -- jwt_token not keepalive
53+
end
54+
headers.Authorization = self.jwt_token
55+
end
56+
3957
local http_cli, err = utils.http.new()
4058
if err then
4159
return nil, err
@@ -51,6 +69,8 @@ local function _request_uri(self, method, uri, opts, timeout)
5169
res, err = http_cli:request_uri(uri, {
5270
method = method,
5371
body = body,
72+
headers = headers,
73+
keepalive = keepalive,
5474
})
5575

5676
if err then
@@ -88,6 +108,8 @@ function _M.new(opts)
88108
local api_prefix = opts.api_prefix
89109
local key_prefix = opts.key_prefix or "/apisix"
90110
local http_host = opts.http_host
111+
local user = opts.user
112+
local password = opts.password
91113

92114
if not typeof.uint(timeout) then
93115
return nil, 'opts.timeout must be unsigned integer'
@@ -109,6 +131,14 @@ function _M.new(opts)
109131
return nil, 'opts.key_prefix must be string'
110132
end
111133

134+
if user and not typeof.string(user) then
135+
return nil, 'opts.user must be string or ignore'
136+
end
137+
138+
if password and not typeof.string(password) then
139+
return nil, 'opts.password must be string or ignore'
140+
end
141+
112142
local endpoints = {}
113143
local http_hosts
114144
if type(http_host) == 'string' then -- signle node
@@ -118,7 +148,7 @@ function _M.new(opts)
118148
end
119149

120150
for _, host in ipairs(http_hosts) do
121-
local m, err = re_match(http_host, [[\/\/([\d.\w]+):(\d+)]], "jo")
151+
local m, err = re_match(host, [[\/\/([\d.\w]+):(\d+)]], "jo")
122152
if not m then
123153
return nil, "invalid http host: " .. err
124154
end
@@ -134,10 +164,15 @@ function _M.new(opts)
134164
end
135165

136166
return setmetatable({
137-
timeout = timeout,
138-
ttl = ttl,
139-
is_cluster= #endpoints > 1,
140-
endpoints = endpoints,
167+
last_auth_time = now(), -- save last Authentication time
168+
jwt_token = nil, -- last Authentication token
169+
is_auth = not not (user and password),
170+
user = user,
171+
password = password,
172+
timeout = timeout,
173+
ttl = ttl,
174+
is_cluster = #endpoints > 1,
175+
endpoints = endpoints,
141176
},
142177
mt)
143178
end
@@ -159,6 +194,37 @@ local function choose_endpoint(self)
159194
return endpoints[pos]
160195
end
161196

197+
-- return refresh_is_ok, error
198+
function refresh_jwt_token(self)
199+
-- token exist and not expire
200+
-- default is 5min, we use 3min
201+
-- https://github.com/etcd-io/etcd/issues/8287
202+
if self.jwt_token and now() - self.last_auth_time < 60 * 3 then
203+
return true, nil
204+
end
205+
206+
local opts = {
207+
body = {
208+
name = self.user,
209+
password = self.password,
210+
}
211+
}
212+
local res, err = _request_uri(self, 'POST',
213+
choose_endpoint(self).full_prefix .. "/auth/authenticate",
214+
opts, 5, true) -- default authenticate timeout 5 second
215+
if err then
216+
return nil, err
217+
end
218+
219+
if not res or not res.body or not res.body.token then
220+
return nil, 'authenticate refresh token fail'
221+
end
222+
223+
self.jwt_token = res.body.token
224+
self.last_auth_time = now()
225+
226+
return true, nil
227+
end
162228

163229
local function set(self, key, val, attr)
164230
-- verify key
@@ -317,7 +383,7 @@ local function get(self, key, attr)
317383
choose_endpoint(self).full_prefix .. "/kv/range",
318384
opts, attr and attr.timeout or self.timeout)
319385

320-
if res.status==200 then
386+
if res and res.status==200 then
321387
if res.body.kvs and tab_nkeys(res.body.kvs)>0 then
322388
for _, kv in ipairs(res.body.kvs) do
323389
kv.key = decode_base64(kv.key)
@@ -383,7 +449,7 @@ end
383449

384450

385451
local function request_chunk(self, method, host, port, path, opts, timeout)
386-
local body, err
452+
local body, err, _
387453
if opts and opts.body and tab_nkeys(opts.body) > 0 then
388454
body, err = encode_json(opts.body)
389455
if not body then
@@ -396,6 +462,16 @@ local function request_chunk(self, method, host, port, path, opts, timeout)
396462
query = encode_args(opts.query)
397463
end
398464

465+
local headers = {}
466+
if self.is_auth then
467+
-- authentication reqeust not need auth request
468+
_, err = refresh_jwt_token(self)
469+
if err then
470+
return nil, err
471+
end
472+
headers.Authorization = self.jwt_token
473+
end
474+
399475
local http_cli
400476
http_cli, err = utils.http.new()
401477
if err then
@@ -417,10 +493,11 @@ local function request_chunk(self, method, host, port, path, opts, timeout)
417493

418494
local res
419495
res, err = http_cli:request({
420-
method = method,
421-
path = path,
422-
body = body,
423-
query = query,
496+
method = method,
497+
path = path,
498+
body = body,
499+
query = query,
500+
headers = headers,
424501
})
425502
utils.log_info("http request method: ", method, " path: ", path,
426503
" body: ", body, " query: ", query)

t/v2/add-auth.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
curl -X "PUT" "${AUTH_ENDPOINT_V2}/v2/auth/users/root" -H "Content-Type: application/json; charset=utf-8" -d $"{\"user\":\"${AUTH_USER}\",\"password\":\"${AUTH_PWD}\"}"
3+
curl -X "PUT" "${AUTH_ENDPOINT_V2}/v2/auth/enable"
4+
curl -X "PUT" "${AUTH_ENDPOINT_V2}/v2/auth/roles/guest" -H "Content-Type: text/plain; charset=utf-8" -u "${AUTH_USER}:${AUTH_PWD}" -d $'{"role":"guest","revoke":{"kv":{"write": ["/*"]}}}'

t/v2/cluster.t

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,28 @@ checked error msg as expect: Key not found
8181
all done
8282
8383
84-
85-
=== TEST 2: cluster set + delete + get
84+
=== TEST 2: cluster set + delete + get + auth
8685
--- http_config eval: $::HttpConfig
8786
--- config
8887
location /t {
8988
content_by_lua_block {
9089
local etcd, err = require "resty.etcd" .new({
9190
http_host = {
92-
"http://127.0.0.1:12379",
91+
"http://127.0.0.1:12379",
9392
"http://127.0.0.1:22379",
9493
"http://127.0.0.1:32379",
95-
}
94+
},
95+
user = 'root',
96+
password = 'abc123',
9697
})
9798
check_res(etcd, err)
9899
99100
local res, err = etcd:set("/test", {a = "abc"})
100101
check_res(res, err)
101102
103+
local res, err = etcd:get("/test")
104+
check_res(res, err)
105+
102106
ngx.sleep(1)
103107
104108
res, err = etcd:delete("/test")
@@ -109,6 +113,18 @@ all done
109113
local data, err = etcd:get("/test")
110114
check_res(data, err, nil, "Key not found")
111115
116+
etcd, err = require "resty.etcd" .new({
117+
http_host = {
118+
"http://127.0.0.1:12379",
119+
"http://127.0.0.1:22379",
120+
"http://127.0.0.1:32379",
121+
},
122+
user = 'wrong_user_name',
123+
password = 'wrong_password',
124+
})
125+
data, err = etcd:get("/test")
126+
check_res(data, err, nil, "The request requires user authentication")
127+
112128
ngx.say("all done")
113129
}
114130
}
@@ -118,4 +134,5 @@ GET /t
118134
[error]
119135
--- response_body
120136
checked error msg as expect: Key not found
137+
checked error msg as expect: The request requires user authentication
121138
all done

t/v3/add-auth.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
export ETCDCTL_API=3
3+
etcdctl version
4+
etcdctl --endpoints="${AUTH_ENDPOINT_V3}" user add "${AUTH_USER}:${AUTH_PWD}"
5+
etcdctl --endpoints="${AUTH_ENDPOINT_V3}" role add root
6+
etcdctl --endpoints="${AUTH_ENDPOINT_V3}" user grant-role root root
7+
etcdctl --endpoints="${AUTH_ENDPOINT_V3}" role list
8+
etcdctl --endpoints="${AUTH_ENDPOINT_V3}" user list
9+
etcdctl --endpoints="${AUTH_ENDPOINT_V3}" auth enable

0 commit comments

Comments
 (0)