Skip to content

Commit fe8caaf

Browse files
piotrppintsized
authored andcommitted
Add support for usernames (Redis ACL)
1 parent e86e12b commit fe8caaf

File tree

5 files changed

+68
-16
lines changed

5 files changed

+68
-16
lines changed

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ OPENRESTY_PREFIX = /usr/local/openresty
44

55
TEST_FILE ?= t
66
TMP_DIR ?= /tmp
7-
SENTINEL_TEST_FILE ?= $(TEST_FILE)/sentinel
87

98
REDIS_CMD = redis-server
109
SENTINEL_CMD = $(REDIS_CMD) --sentinel

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,22 @@ If the `params.url` field is present then it will be parsed to set the other par
8181

8282
The format for connecting directly to Redis is:
8383

84-
`redis://PASSWORD@HOST:PORT/DB`
84+
`redis://USERNAME:PASSWORD@HOST:PORT/DB`
8585

86-
The `PASSWORD` and `DB` fields are optional, all other components are required.
86+
The `USERNAME`, `PASSWORD` and `DB` fields are optional, all other components are required.
87+
88+
Use of username requires Redis 6.0.0 or newer.
8789

8890
### Connections via Redis Sentinel
8991

9092
When connecting via Redis Sentinel, the format is as follows:
9193

92-
`sentinel://PASSWORD@MASTER_NAME:ROLE/DB`
94+
`sentinel://USERNAME:PASSWORD@MASTER_NAME:ROLE/DB`
9395

94-
Again, `PASSWORD` and `DB` are optional. `ROLE` must be either `m` or `s` for master / slave respectively.
96+
Again, `USERNAME`, `PASSWORD` and `DB` are optional. `ROLE` must be either `m` or `s` for master / slave respectively.
9597

9698
On versions of Redis newer than 5.0.1, Sentinels can optionally require their own password. If enabled, provide this password in the `sentinel_password` parameter.
99+
On Redis 6.2.0 and newer you can pass username using `sentinel_username` parameter.
97100

98101
A table of `sentinels` must also be supplied. e.g.
99102

@@ -103,6 +106,7 @@ local redis, err = rc:connect{
103106
sentinels = {
104107
{ host = "127.0.0.1", port = 26379 },
105108
},
109+
sentinel_username = "default",
106110
sentinel_password = "password"
107111
}
108112
```
@@ -137,7 +141,9 @@ If configured as a table of commands, the command methods will be replaced by a
137141
host = "127.0.0.1",
138142
port = "6379",
139143
path = "", -- unix socket path, e.g. /tmp/redis.sock
144+
username = "",
140145
password = "",
146+
sentinel_username = "",
141147
sentinel_password = "",
142148
db = 0,
143149

lib/resty/redis/connector.lua

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local ngx_ERR = ngx.ERR
66
local ngx_re_match = ngx.re.match
77

88
local str_find = string.find
9+
local str_sub = string.sub
910
local tbl_remove = table.remove
1011
local tbl_sort = table.sort
1112
local ok, tbl_new = pcall(require, "table.new")
@@ -95,7 +96,9 @@ local DEFAULTS = setmetatable({
9596
host = "127.0.0.1",
9697
port = 6379,
9798
path = "", -- /tmp/redis.sock
99+
username = "",
98100
password = "",
101+
sentinel_username = "",
99102
sentinel_password = "",
100103
db = 0,
101104
url = "", -- DSN url
@@ -149,14 +152,14 @@ local function parse_dsn(params)
149152
fields = { "password", "master_name", "role", "db" }
150153
end
151154

152-
-- password may not be present
155+
-- username/password may not be present
153156
if #m < 5 then tbl_remove(fields, 1) end
154157

155158
local roles = { m = "master", s = "slave" }
156159

157160
local parsed_params = {}
158161

159-
for i,v in ipairs(fields) do
162+
for i, v in ipairs(fields) do
160163
if v == "db" or v == "port" then
161164
parsed_params[v] = tonumber(m[i + 1])
162165
else
@@ -168,6 +171,12 @@ local function parse_dsn(params)
168171
end
169172
end
170173

174+
local colon_pos = str_find(parsed_params.password or "", ":", 1, true)
175+
if colon_pos then
176+
parsed_params.username = str_sub(parsed_params.password, 1, colon_pos - 1)
177+
parsed_params.password = str_sub(parsed_params.password, colon_pos + 1)
178+
end
179+
171180
return tbl_copy_merge_defaults(params, parsed_params)
172181
end
173182

@@ -233,10 +242,13 @@ function _M.connect_via_sentinel(self, params)
233242
local master_name = params.master_name
234243
local role = params.role
235244
local db = params.db
245+
local username = params.username
236246
local password = params.password
247+
local sentinel_username = params.sentinel_username
237248
local sentinel_password = params.sentinel_password
238249
if sentinel_password then
239-
for _,host in ipairs(sentinels) do
250+
for _, host in ipairs(sentinels) do
251+
host.username = sentinel_username
240252
host.password = sentinel_password
241253
end
242254
end
@@ -255,6 +267,7 @@ function _M.connect_via_sentinel(self, params)
255267
sentnl:set_keepalive()
256268

257269
master.db = db
270+
master.username = username
258271
master.password = password
259272

260273
local redis, err = self:connect_to_host(master)
@@ -278,6 +291,7 @@ function _M.connect_via_sentinel(self, params)
278291
if db or password then
279292
for _, slave in ipairs(slaves) do
280293
slave.db = db
294+
slave.username = username
281295
slave.password = password
282296
end
283297
end
@@ -356,9 +370,16 @@ function _M.connect_to_host(self, host)
356370
else
357371
r:set_timeout(config.read_timeout)
358372

373+
local username = host.username
359374
local password = host.password
360375
if password and password ~= "" then
361-
local res, err = r:auth(password)
376+
local res
377+
-- usernames are supported only on Redis 6+, so use new AUTH form only when absolutely necessary
378+
if username and username ~= "" and username ~= "default" then
379+
res, err = r:auth(username, password)
380+
else
381+
res, err = r:auth(password)
382+
end
362383
if err then
363384
return res, err
364385
end

t/config.t

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ location /t {
120120
host = "127.0.0.1",
121121
port = $TEST_NGINX_REDIS_PORT,
122122
path = "",
123+
username = "",
123124
password = "",
124125
db = 0,
125126

@@ -144,6 +145,7 @@ location /t {
144145
host = "127.0.0.1",
145146
port = $TEST_NGINX_REDIS_PORT,
146147
path = "",
148+
username = "",
147149
password = "",
148150
db = 0,
149151

t/connector.t

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,30 @@ GET /t
213213
--- no_error_log
214214
[error]
215215

216+
=== TEST 7: username and password
217+
--- http_config eval: $::HttpConfig
218+
--- config
219+
location /t {
220+
lua_socket_log_errors Off;
221+
content_by_lua_block {
222+
local rc = require("resty.redis.connector").new({
223+
port = $TEST_NGINX_REDIS_PORT,
224+
username = "x",
225+
password = "foo",
226+
})
227+
228+
local redis, err = rc:connect()
229+
assert(not redis and string.find(err, "WRONGPASS"),
230+
"connect should fail with invalid username-password error")
231+
}
232+
}
233+
--- request
234+
GET /t
235+
--- no_error_log
236+
[error]
237+
216238

217-
=== TEST 7: Bad unix domain socket path should fail
239+
=== TEST 8: Bad unix domain socket path should fail
218240
--- http_config eval: $::HttpConfig
219241
--- config
220242
location /t {
@@ -234,7 +256,7 @@ GET /t
234256
[error]
235257

236258

237-
=== TEST 7.1: Good unix domain socket path should succeed
259+
=== TEST 8.1: Good unix domain socket path should succeed
238260
--- http_config eval: $::HttpConfig
239261
--- config
240262
location /t {
@@ -256,7 +278,7 @@ GET /t
256278
[error]
257279

258280

259-
=== TEST 8: parse_dsn
281+
=== TEST 9: parse_dsn
260282
--- http_config eval: $::HttpConfig
261283
--- config
262284
location /t {
@@ -280,7 +302,7 @@ location /t {
280302

281303

282304
local user_params = {
283-
url = "sentinel://foo@foomaster:s/2"
305+
url = "sentinel://foo:bar@foomaster:s/2"
284306
}
285307

286308
local params, err = rc.parse_dsn(user_params)
@@ -290,6 +312,8 @@ location /t {
290312
assert(params.master_name == "foomaster", "master_name should be foomaster")
291313
assert(params.role == "slave", "role should be slave")
292314
assert(tonumber(params.db) == 2, "db should be 2")
315+
assert(params.username == "foo", "username should be foo")
316+
assert(params.password == "bar", "password should be bar")
293317

294318

295319
local params = {
@@ -307,7 +331,7 @@ GET /t
307331
[error]
308332

309333

310-
=== TEST 9: params override dsn components
334+
=== TEST 10: params override dsn components
311335
--- http_config eval: $::HttpConfig
312336
--- config
313337
location /t {
@@ -340,7 +364,7 @@ GET /t
340364
[error]
341365

342366

343-
=== TEST 9: Integration test for parse_dsn
367+
=== TEST 11: Integration test for parse_dsn
344368
--- http_config eval: $::HttpConfig
345369
--- config
346370
location /t {
@@ -385,7 +409,7 @@ GET /t
385409
[error]
386410

387411

388-
=== TEST 10: DSN without DB
412+
=== TEST 12: DSN without DB
389413
--- http_config eval: $::HttpConfig
390414
--- config
391415
location /t {

0 commit comments

Comments
 (0)