來源:redis.io
翻譯:Wen Hui
轉(zhuǎn)載:中間件小哥
Redis訪問控制列表(ACL),是一項可以實現(xiàn)限制特定客戶端連接可執(zhí)行命令和鍵訪問的功能,它的工作方式是:客戶端連接服務(wù)器以后,客戶端需要提供用戶名和密碼用于驗證身份:如果驗證成功,客戶端連接會關(guān)聯(lián)特定用戶以及用戶相應(yīng)的權(quán)限。Redis可以配置新的客戶端連接自動使用default用戶進行驗證(默認選項),因此配置default用戶權(quán)限
會使沒有驗證的客戶端只能使用一小部分功能。
Redis 6版本(第一個支持ACL的版本)的默認配置和之前版本完全相同,即每一個新的客戶端連接有權(quán)限去訪問所用命令和鍵,因此ACL功能對舊版客戶端和應(yīng)用是向后兼容(backward compatible)的,并且對舊版配置用戶密碼的方式,使用requirepass配置選項是完全支持的,但是不同的是requirepass配置選項只是設(shè)定default用戶的密碼。
Redis AUTH命令在Redis 6版本進行擴展,現(xiàn)在可以使用兩個參數(shù)形式:
AUTH <username> <password>
如果使用舊版本的使用方式:
AUTH <password>
會使用 default用戶進行驗證,所以用這種形式進行驗證意味著我們想使用default用戶進行身份驗證,這種方式可以提供完美的向后兼容舊版本Redis的支持。
ACL 的使用場景:
在使用ACL之前你需要問一下自己使用這層訪問控制的目的是什么。正常來說ACL可以實現(xiàn)下面兩個重要目標:
1. 你希望限制用戶訪問命令和鍵以提高安全性,因此不在信任列表里的用戶沒有權(quán)限訪問,而在信任列表里的用戶有可以完成工作的最小訪問權(quán)限。例如一些客戶端只可以執(zhí)行只讀的命令。
2. 你希望提高運營安全, 當程序出錯或人為原因操作失誤, 進程或用戶不允許訪問Redis,以避免數(shù)據(jù)或者配置受到損壞。 例如一個用于拿取延遲工作的工作客戶端不應(yīng)該有權(quán)限去執(zhí)行flushall命令。
另外一個ACL的應(yīng)用場景是關(guān)于管理Redis實例。Redis通常被作為內(nèi)部托管服務(wù)提供給公司內(nèi)部用戶或者作為云服務(wù)提供商配置的軟件服務(wù)。在這兩種設(shè)置方式中我們必須確保配置命令對用戶來說是不可見的。過去版本的redis是暫時通過命令重命名(command renaming)這種方式實現(xiàn)的,是舊版本沒有ACL支持的一種臨時措施,但是卻不是完美的解決方案。
使用ACL命令配置ACL
Acl 是用DSL(domain specific language)語言來定義用戶是否有權(quán)限訪問特定資源或操作的。每一條ACL規(guī)則都是從第一個到最后一個,從左至右來實現(xiàn)的,因為在一些時候,定義規(guī)則的順序會決定用戶是否有權(quán)限去訪問特定資源和操作。
默認配置下只有一個用戶被定義(default用戶)。我們可以使用 ACL LIST命令來檢查當前有效的ACL規(guī)則。使用ACL LIST確認剛啟動的,使用默認配置的Redis 實例ACL規(guī)則如下:
> ACL LIST
1) "user default on nopass ~* +@all"
上面的命令遵循和Redis配置文件同樣的格式返回當前ACL配置的規(guī)則。
每一行最前面的兩個詞是”user”和用戶名。之后的詞是具體ACL定義的規(guī)則。 我們下面將會看到怎樣使用這些規(guī)則,但是現(xiàn)在,可以簡單理解為默認(default)用戶的配置是開啟的(active(on)),不需要密碼(require no password(nopass)),可以訪問所有鍵(to access every possible key(~*))和可以執(zhí)行任何命令(call every possible command(+@all))的。
另外,對于默認(default)用戶來說,沒有配置密碼規(guī)則意味著新的客戶端連接會自動使用默認(default)進行驗證而不用顯式的執(zhí)行AUTH命令。
ACL規(guī)則
下面是有效的ACL規(guī)則,特定規(guī)則只是用于啟用或刪除特定ACL標志的詞,或者用于改變用戶當前的ACL規(guī)則。其他的規(guī)則是char 類型的前綴和命令或命令類別的名字,或者鍵的模式。
啟用和禁止用戶:
on:啟用用戶:可以使用這個用戶進行驗證。
off:禁止用戶:不能使用這個用戶進行驗證,但已經(jīng)驗證的客戶端連接仍然可以繼續(xù)工作,需要注意的是如果默認(default)用戶如果被禁止,默認用戶配置將不起作用,新的客戶端連接將會不被驗證并且需要用戶顯式發(fā)送AUTH命令或HELLO命令進行驗證。
允許和禁止命令:
+<command>: 添加命令到用戶允許執(zhí)行命令列表。
-<command>: 從用戶允許執(zhí)行命令列表刪除命令。
+@<category>: 允許用戶執(zhí)行所有定義在category類別中的命令。有效的類別例如@admin, @set, @sortedset等等。你可以通過使用ACL CAT命令查看所有預定義的類別,另外一個特殊的類別+@all意思是所有在當前系統(tǒng)里的命令,以及將來在Redis模塊中添加的命令。
-@<category>: 類似于+@<category> ,但是是從用戶可以執(zhí)行的命令列表中刪除特定的命令。
+<command>|subcommand:允許 用戶啟用一個被禁止命令的子命令,請注意這個命令不允許使用-規(guī)則,例如-DEBUG|SEGFAULT,只能使用+規(guī)則。如果整個命令已經(jīng)被啟用,單獨啟用子命令沒有意義,Redis會返回錯誤。
allcommands: +@all的別名. 注意這個會使用戶允許執(zhí)行所有從Redis模塊中添加的命令。
nocommands: -@all的別名。
允許和禁止特定的鍵
`<pattern>`:添加一個鍵模式以被用在用戶執(zhí)行命令里面。例如允許使用所有的鍵。鍵的模式是通配符模式,像KEYS命令一樣。另外使用多個模式也是被允許的, allkeys:是~的別名, resetkeys:在鍵模式列表里面清空所有的鍵模式,例如ACL ~foo:* ~bar:* resetkeys ~objects:*,將會使用戶端只有權(quán)限訪問objects:* 的鍵模式。
配置有效的用戶名密碼:
><password>:添加密碼到用戶有效密碼列表里,例如>mypass將添加mypass到用戶有效密碼列表,這個配置會使nopass失效,每個用戶都可以配置任何數(shù)量的密碼。
<<password>:從用戶有效密碼列表中刪除密碼,Redis會返回錯誤如果這個密碼在之前沒有被設(shè)置的話。
#<hash>:添加SHA-256形式哈希值到用戶有效密碼列表里,這種方式允許用戶使用哈希值在acl.conf中存儲密碼,以避免明文形式存儲。只有SHA-256形式哈希值才能被當做密碼的哈希值,并且只能是64字符長度,并且使用16進制編碼的小寫字符。
!<hash>:從有效的密碼列表中刪除哈希值密碼,這種方式在你不知道密碼明文的情況下卻又想刪除密碼的時候非常有用。
nopass:刪除所有與用戶關(guān)聯(lián)的密碼,并且用戶會被標記不需要密碼驗證:這意味著任何密碼都會通過用戶驗證。如果在默認(default)用戶使用這個配置,所有客戶端連接會立即通過默認(default)用戶驗證。注意resetpass配置會清除這個配置。
resetpass:清除用戶所有密碼,另外會清除用戶nopass狀態(tài)。使用resetpass后用戶沒有與其相關(guān)聯(lián)的密碼,這種方式下用戶如果不添加新密碼的話(或在后面設(shè)置nopass)則無法進行驗證。
注意:未使用nopass進行標記且沒有有效密碼列表的用戶實際上是無法使用的,因為將無法以該用戶身份登錄。
重設(shè)用戶:
reset:reset和以下操作等同:resetpass, resetkeys, off, -@all.用戶將返回和它被默認創(chuàng)建時同樣的狀態(tài)。
使用ACL SETUSER創(chuàng)建和修改用戶當前ACL配置
用戶可以以下面兩種方式被創(chuàng)建和修改:
1. 使用ACL命令和ACL SETUSER子命令。
2. 修改Redis服務(wù)器配置,用戶可以在那里被定義,然后重啟服務(wù)器并生效。 或者使用外部ACL文件,使用ACL LOAD 來導入ACL信息。
在這部分我們會學到怎樣使用ACL命令來定義用戶。如果我們學會用這種方式定義用戶轉(zhuǎn)換為用配置文件配置用戶就會很簡單。 使用配置文件定義用戶會在下面的章節(jié)單獨列出。
開始我們使用最簡單的方式定義一個ACL用戶:
ACL SETUSER alice
OK
SETUSER命令需要提供用戶名以及與用戶相關(guān)聯(lián)的ACL規(guī)則。但是在上面的例子里我們沒有提供任何與用戶相關(guān)聯(lián)的規(guī)則。這樣我們只是創(chuàng)建一個用戶,如果用戶不存在的話,會使用默認ACL配置屬性創(chuàng)建。 如果用戶已經(jīng)存在,這條命令將不起任何作用。
讓我們來看一下默認用戶的狀態(tài):
ACL LIST
"user alice off -@all"
"user default on nopass ~* +@all"
剛剛創(chuàng)建的alice用戶狀態(tài)是:
Off關(guān)閉狀態(tài):意思是用戶被禁用,AUTH命令將不會起作用。
不能使用任何命令,注意默認創(chuàng)建的用戶沒有使用任何命令的權(quán)限,所以-@all在上面的輸出可以忽略,但是ACL LIST會用更顯式的方式來告訴用戶。
最后用戶不能訪問任何鍵模式。
用戶沒有任何密碼設(shè)置。
這樣的用戶是完全沒有用的。讓我們來定義一個開啟的,設(shè)置密碼的,只能訪問GET命令并只能訪問cached:鍵模式的用戶。
ACL SETUSER alice on >p1pp0 ~cached:* +get
OK
現(xiàn)在用戶可以做一些事情,但如果做其他的事情會被拒絕:
AUTH alice p1pp0
OK
GET foo
(error) NOPERM this user has no permissions to access one of the keys used as arguments
GET cached:1234
(nil)
SET cached:1234 zap
(error) NOPERM this user has no permissions to run the 'set' command or its subcommnad
命令像預期想象的一樣可以工作,為了檢查用戶alice的配置屬性(記住用戶名是區(qū)分大小寫的),可以使用一個對計算機更友好的替代ACL LIST命令,ACL LIST的輸出被認為是對人更友好的。
ACL GETUSER alice
"flags"
- "on"
"passwords"
- "2d9c75..."
"commands"
"-@all +get"
"keys"
- "cached:*"
ACL GETUSER命令返回的是更容易用電腦進行提取的鍵值對。輸出包括用戶標志,鍵模式列表,密碼列表等。如果我們使用RESP3這種輸出會更友好,如果使用RESP3方式,返回值如下:
ACL GETUSER alice
1# "flags" => 1~ "on"
2# "passwords" => 1) "2d9c75..."
3# "commands" => "-@all +get"
4# "keys" => 1) "cached:*"
注意:從現(xiàn)在開始我們會繼續(xù)使用Redis默認協(xié)議,版本2,因為轉(zhuǎn)換到版本3對于社區(qū)和使用者來說還需要一段時間。
使用另一個ACL SETUSER命令(在另一個用戶下執(zhí)行,因為Alice沒有權(quán)限運行ACL命令)我們可以為用戶增加更多的模式:
ACL SETUSER alice ~objects:* ~items:* ~public:*
OK
ACL LIST
"user alice on >2d9c75... ~cached:* ~objects:* ~items:* ~public:* -@all +get"
"user default on nopass ~* +@all"
現(xiàn)在用戶的表述和和我們想象的完全一樣。
多次調(diào)用ACL SETUSER命令時的情況
在這里需要特別理解如果多次調(diào)用ACL SETUSER命令時的情況。在多次調(diào)ACL SETUSER時,SETUSER命令不會重新設(shè)定用戶,而是在之前設(shè)定的基礎(chǔ)上應(yīng)用新的ACL規(guī)則。用戶只會在之前不存在的時候被重新設(shè)定:在這種情況下,一個新的用戶會被創(chuàng)建,沒有關(guān)聯(lián)任何ACL規(guī)則,換句話說,用戶不能做任何事情,是被禁用的,并且沒有密碼相關(guān)聯(lián):從安全的角度上來說這種默認方式是最好的。
但是后面的ACL SETUSER調(diào)用只會在之前的基礎(chǔ)上增加ACL規(guī)則,例如下面的例子:
ACL SETUSER myuser +set
OK
ACL SETUSER myuser +get
OK
會賦予用戶調(diào)用GET和SET的權(quán)限。
ACL LIST
"user default on nopass ~* +@all"
"user myuser off -@all +set +get"
使用命令類別:
設(shè)置用戶ACL規(guī)則用逐步添加命令的方式是件很懊惱的事情,所以我們會用下面一種方式:
ACL SETUSER antirez on +@all -@dangerous >42a979... ~*
通過使用+ @ all和-@ dangerous,我們包含了所有命令,然后在Redis命令表中刪除了所有標記為危險的命令。請注意,除了+ @ all外,命令類別從不包含模塊命令。如果使用+ @ all,則所有命令都可以由用戶執(zhí)行,甚至未來通過Redis模塊加載的命令也可以執(zhí)行。但是,如果使用ACL規(guī)則+ @ readonly或其他任何規(guī)則,則始終會排除Redis模塊命令。這一點非常重要,因為我們只應(yīng)該信任redis原生的命令。Redis模塊有些時候會引起程序執(zhí)行的風險,并且在ACL僅是+的情況下,即+ @ all -...的形式,我們應(yīng)該絕對確??蓤?zhí)行命令中不會有危險的命令。
但是我們要記住要命令類別,并且命令類別中確切包含的命令是不可能的。因此Redis ACL命令導出CAT子命令,該命令可以兩種形式使用:
ACL CAT -- Will just list all the categories available
ACL CAT <category-name> -- Will list all the commands inside the category
例子:
ACL CAT
"keyspace"
"read"
"write"
"set"
"sortedset"
"list"
"hash"
"string"
"bitmap"
"hyperloglog"
"geo"
"stream"
"pubsub"
"admin"
"fast"
"slow"
"blocking"
"dangerous"
"connection"
"transaction"
"scripting"
我們會看到一共有21個類別,下面讓我們看一下geo類別中包含的命令:
ACL CAT geo
"geohash"
"georadius_ro"
"georadiusbymember"
"geopos"
"geoadd"
"georadiusbymember_ro"
"geodist"
"georadius"
注意命令可能同時屬于不同的類別,所以ACL規(guī)則例如+@geo -@readonly會導致特定的geo命令被排除,因為它們也屬于readonly類別。
增加子命令:
通常來說提供將命令整體加入ACL可執(zhí)行命令列表或者從列表中刪除是不夠的,很多Redis命令通過子命令做很多不同的事。例如CLIENT命令可以被用于執(zhí)行危險或不危險的操作。很多項目部署不會將CLIENT KILL交給非管理員用戶執(zhí)行,但是卻允許它們使用CLIENT SETNAME來設(shè)置連接用戶屬性。
注意:新的RESP3協(xié)議的 HELLO命令將來會提供一個SETNAME的選項,但是這個還是一個好的例子。
在這種情況下我們可以用下面的方式改變ACL規(guī)則:
ACL SETUSER myuser -client +client|setname +client|getname
我們先刪除CLIENT命令,然后再加上兩個可以執(zhí)行的子命令。注意我們不能改變這個順序,子命令只可以添加而不能刪除, 這樣設(shè)計的原因是也許將來會有更多的子命令被定義,顯式的指定哪些子命令用戶可以被執(zhí)行會安全得多。另外,如果添加子命令的父命令不是被禁止的,會產(chǎn)生錯誤,因為在這種方式下只可能是用戶定義的ACL規(guī)則有錯誤。
ACL SETUSER default +debug|segfault
(error) ERR Error in ACL SETUSER modifier '+debug|segfault': Adding a
subcommand of a command already fully added is not allowed. Remove the
command to start. Example: -DEBUG +DEBUG|DIGEST
值得注意的是子命令匹配會帶來一些服務(wù)器性能效率上的問題,但是這類的問題即使使用合成性能測試基準也非常難測量,當這類命令執(zhí)行的時候只有額外cpu被消耗,其他命令則不會被消耗。
+@all 和 -@all對比
在上一章節(jié)我們看到怎樣通過添加刪除單獨命令來定義acl命令規(guī)則。
密碼在內(nèi)部是怎樣存儲的
Redis內(nèi)部通過SHA256哈希算法存儲密碼,如果你定義一個密碼并且查看ACL LIST 或GETUSER的命令輸出,你會看到一個看起來像偽隨機的16進制字符串。下面有個例子,因為在之前的例子為了簡單起見16進制字符串只截取了前一部分。
ACL GETUSER default
"flags"
- "on"
"allkeys"
"allcommands"
"passwords"
- "2d9c75273d72b32df726fb545c8a4edc719f0a95a6fd993950b10c474ad9c927"
"commands"
"+@all"
"keys"
- "*"
并且舊命令CONFIG GET requirepass從redis 6版本開始不會返回明文密碼,而是會返回加密的密碼。
適用SHA256算法可以避免存儲明文密碼,與此同時,也支持非??斓膱?zhí)行AUTH命令,這也是Redis重要的特點也是客戶希望從Redis得到的。
但是ACL密碼不是真正的密碼,而是客戶端和服務(wù)器端的共享秘鑰。因為這個原因密碼不是一個客戶使用的驗證令牌(authentication token)。 例如:
密碼沒有長度限制,密碼只是存儲在客戶端的一些軟件里,人們不會需要從這里獲取密碼。
ACL密碼不會用于保護其他任何東西,例如永遠不會作為一些電子郵箱的密碼。
經(jīng)常的如果你可以訪問密碼的哈希值,并擁有訪問特定服務(wù)器權(quán)限,或者破壞系統(tǒng)本身,你已經(jīng)可以訪問密碼所保護的實體,Redis實例的穩(wěn)定和它所存儲的數(shù)據(jù)。
因為這個原因去減慢密碼驗證速度而去使用耗時和耗空間的算法去追求密碼的安全性,是一個非常不明智的選擇。我們建議的方式是生成一個難以破解的密碼,因此即使使用哈希算法也沒有人可以使用字典或暴力破解去破解密碼。因為這個原因,一個特別的ACL命令使用系統(tǒng)的偽加密生成算法去生成密碼。
ACL GENPASS
"0e8ad12c1962355a3eb35e0ca686343b"
這個命令會輸出16字節(jié)(128位)隨機字符串轉(zhuǎn)換為32字節(jié)字母數(shù)字字符串。這個長度密碼完全可以避免被攻擊并且足夠短被管理,復制粘貼,存儲等等。這個是你生成Redis密碼應(yīng)該被用到的。
使用外部ACL文件
通過Redis配置方式存儲ACL用戶有以下兩種方式:
1. 用戶可以直接在Redis.conf文件中被指定。
2. 可以通過外部ACL文件指定。
這兩種方式是互相不兼容的,Redis會讓你選擇其中的一種方式。在redis.conf中配置用戶是非常簡單的,適用于簡易的應(yīng)用場景。如果需要有很多用戶去設(shè)定,在復雜的環(huán)境中,我們強烈建議使用ACL文件。
Redis.conf 和外部ACL文件是完全一樣的。所以從一個轉(zhuǎn)換到另一個是非常簡單的。
下面的例子:
user <username> ... acl rules ...
例如:
user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
當你希望使用外部ACL文件,你需要指定配置選項aclfile,像下面這樣:
aclfile /etc/redis/users.acl
當你直接在redis.conf中直接指定一些用戶的時候,你可以使用CONFIG REWRITE來覆蓋存儲文件中新的用戶配置。
外部ACL文件具有更加強大的功能,你可以像下面這樣:
使用ACL LOAD如果你手動的改變ACL文件并且希望Redis重新讀取新的配置,注意命令能夠讀取文件的前提是所有用戶在文件中都被正確設(shè)置。不然的話會返回錯誤,并且舊的配置會繼續(xù)生效。
適用ACL SAVE來存儲當前ACL配置到ACL文件中。
注意CONFIG REWRITE不會自動調(diào)用ACL SAVE:當你使用ACL文件的時候配置和ACL是被分開處理的。