@@ -6,6 +6,7 @@ local clear_tab = require("table.clear")
6
6
local utils = require (" resty.etcd.utils" )
7
7
local tab_nkeys = require (" table.nkeys" )
8
8
local encode_args = ngx .encode_args
9
+ local now = ngx .now
9
10
local sub_str = string.sub
10
11
local str_byte = string.byte
11
12
local str_char = string.char
@@ -22,11 +23,13 @@ local INIT_COUNT_RESIZE = 2e8
22
23
23
24
local _M = {}
24
25
25
-
26
26
local mt = { __index = _M }
27
27
28
+ -- define local refresh function variable
29
+ local refresh_jwt_token
30
+
31
+ local function _request_uri (self , method , uri , opts , timeout , ignore_auth )
28
32
29
- local function _request_uri (self , method , uri , opts , timeout )
30
33
local body
31
34
if opts and opts .body and tab_nkeys (opts .body ) > 0 then
32
35
body = encode_json (opts .body ) -- encode_args(opts.body)
@@ -36,6 +39,21 @@ local function _request_uri(self, method, uri, opts, timeout)
36
39
uri = uri .. ' ?' .. encode_args (opts .query )
37
40
end
38
41
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
+
39
57
local http_cli , err = utils .http .new ()
40
58
if err then
41
59
return nil , err
@@ -51,6 +69,8 @@ local function _request_uri(self, method, uri, opts, timeout)
51
69
res , err = http_cli :request_uri (uri , {
52
70
method = method ,
53
71
body = body ,
72
+ headers = headers ,
73
+ keepalive = keepalive ,
54
74
})
55
75
56
76
if err then
@@ -88,6 +108,8 @@ function _M.new(opts)
88
108
local api_prefix = opts .api_prefix
89
109
local key_prefix = opts .key_prefix or " /apisix"
90
110
local http_host = opts .http_host
111
+ local user = opts .user
112
+ local password = opts .password
91
113
92
114
if not typeof .uint (timeout ) then
93
115
return nil , ' opts.timeout must be unsigned integer'
@@ -109,6 +131,14 @@ function _M.new(opts)
109
131
return nil , ' opts.key_prefix must be string'
110
132
end
111
133
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
+
112
142
local endpoints = {}
113
143
local http_hosts
114
144
if type (http_host ) == ' string' then -- signle node
@@ -118,7 +148,7 @@ function _M.new(opts)
118
148
end
119
149
120
150
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" )
122
152
if not m then
123
153
return nil , " invalid http host: " .. err
124
154
end
@@ -134,10 +164,15 @@ function _M.new(opts)
134
164
end
135
165
136
166
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 ,
141
176
},
142
177
mt )
143
178
end
@@ -159,6 +194,37 @@ local function choose_endpoint(self)
159
194
return endpoints [pos ]
160
195
end
161
196
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
162
228
163
229
local function set (self , key , val , attr )
164
230
-- verify key
@@ -317,7 +383,7 @@ local function get(self, key, attr)
317
383
choose_endpoint (self ).full_prefix .. " /kv/range" ,
318
384
opts , attr and attr .timeout or self .timeout )
319
385
320
- if res .status == 200 then
386
+ if res and res .status == 200 then
321
387
if res .body .kvs and tab_nkeys (res .body .kvs )> 0 then
322
388
for _ , kv in ipairs (res .body .kvs ) do
323
389
kv .key = decode_base64 (kv .key )
383
449
384
450
385
451
local function request_chunk (self , method , host , port , path , opts , timeout )
386
- local body , err
452
+ local body , err , _
387
453
if opts and opts .body and tab_nkeys (opts .body ) > 0 then
388
454
body , err = encode_json (opts .body )
389
455
if not body then
@@ -396,6 +462,16 @@ local function request_chunk(self, method, host, port, path, opts, timeout)
396
462
query = encode_args (opts .query )
397
463
end
398
464
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
+
399
475
local http_cli
400
476
http_cli , err = utils .http .new ()
401
477
if err then
@@ -417,10 +493,11 @@ local function request_chunk(self, method, host, port, path, opts, timeout)
417
493
418
494
local res
419
495
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 ,
424
501
})
425
502
utils .log_info (" http request method: " , method , " path: " , path ,
426
503
" body: " , body , " query: " , query )
0 commit comments