概述
不同的用戶只能訪問特定的虛擬主機。他們在每個虛擬主機中的權限也可以被限制。
RabbitMQ支持兩種主要的身份驗證機制,以及幾種身份驗證和授權后端。
專用術語和定義
身份驗證和授權經常被混淆或交替使用。這是錯誤的,在RabbitMQ中,兩者是分開的。為了簡單起見,我們將身份驗證定義為“確定用戶是誰”,而授權定義為“確定用戶可以做什么和不可以做什么”。
基礎
客戶端使用RabbitMQ特性來連接它。每個連接都有一個經過身份驗證的關聯用戶。它還以一個虛擬主機為目標,用戶必須對該虛擬主機擁有一組特定的權限。
用戶憑據、目標虛擬主機和(可選的)客戶端證書在連接啟動時指定。
有一對默認的憑據,稱為默認用戶。默認情況下,該用戶只能用於主機-本地連接。使用它的遠程連接將被拒絕。
生產環境不應該使用默認用戶,而應該使用生成的憑證創建新用戶帳戶。
默認虛擬主機和用戶
當服務器第一次開始運行時,檢測到它的數據庫沒有被初始化或已經被刪除,它會用以下資源初始化一個新的數據庫:
- 一個名為/(斜杠)的虛擬主機,
- 一個名為guest的用戶,默認密碼為guest,被授予對/虛擬主機的完全訪問權
建議使用生成的用戶名和密碼對新用戶進行預配置,或者刪除guest用戶,或者至少將其密碼更改為合理安全的生成值,這樣就不會被公眾所知。
身份驗證:你說你是誰?
應用程序連接到RabbitMQ后,在執行操作之前,必須進行身份驗證,即呈現並證明自己的身份。有了這個身份,RabbitMQ節點就可以查找它的權限,並授權訪問諸如虛擬主機、隊列、exchanges等資源。
驗證客戶端身份的兩種主要方法是用戶名/密碼對和X.509證書。用戶名/密碼對可以用於各種驗證憑據的身份驗證后端。
未通過身份驗證的連接將被關閉,並在服務器日志中顯示一條錯誤消息。
要使用X.509證書驗證客戶端連接,必須啟用內置插件rabbitmq-auth-mechanism-ssl,並且客戶端必須配置為使用EXTERNAL機制。使用這種機制,任何客戶端提供的密碼都將被忽略。
"guest"用戶只能從本地主機連接
默認情況下,guest用戶被禁止從遠程主機連接;它只能通過loopback接口(即localhost)連接。這適用於與協議無關的連接。任何其他用戶(默認情況下)都不會受到這種限制。
在生產系統中,解決這個問題的推薦方法是創建一個新用戶或一組具有訪問必要虛擬主機權限的用戶。這可以通過 CLI tools、HTTP API或定義導入來實現。
這是通過配置文件中的loopback_users項配置的。
可以通過將loopback_users配置設置為none來允許guest用戶從遠程主機連接。
一個簡單的RabbitMQ配置文件,允許客戶端遠程連接,如下所示:
# DANGER ZONE!
#
# allowing remote connections for default user is highly discouraged
# as it dramatically decreases the security of the system. Delete the user
# instead and create a new one with generated secure credentials.
loopback_users = none
管理用戶和權限
用戶和權限可以使用CLI工具和定義導入(下面將介紹)進行管理。
開始之前:Shell轉義和生成的密碼
生成復雜的密碼是一種常見的安全實踐,通常涉及非字母數字字符。這種做法在RabbitMQ用戶中非常適用。
shell (bash、zsh等)解釋某些字符(!, ?, &, ^, ", ', *, ~和其他)作為控制字符。
當rabbitmqctl add_user
、rabbitmqctl change_password
以及其他接受密碼的命令在命令行中指定密碼時,這些控制字符必須在shell中進行適當的轉義。如果轉義不恰當,命令將會失敗,或者RabbitMQ CLI工具將從shell接收到不同的值。
在生成將在命令行上傳遞的密碼時,長(比如,40到100個字符)的字母數字值和一組非常有限的符號(例如:,=)是最安全的選項。
當用戶是通過HTTP API創建而不使用shell(例如curl)時,控制字符限制不適用。但是,根據所使用的編程語言,可能需要不同的轉義規則。
添加用戶
添加用戶使用rabbitmqctl add_user
。它有多種方式指定密碼:
# will prompt for password, only use this option interactively
rabbitmqctl add_user "username"
# Password is provided via standard input.
# Note that certain characters such as $, &, &, #, and so on must be escaped to avoid
# special interpretation by the shell.
echo '2a55f70a841f18b97c3a7db939b7adc9e34a0f1b' | rabbitmqctl add_user 'username'
# Password is provided as a command line argument.
# Note that certain characters such as $, &, &, #, and so on must be escaped to avoid
# special interpretation by the shell.
rabbitmqctl add_user 'username' '2a55f70a841f18b97c3a7db939b7adc9e34a0f1b'
在Windows上,rabbitmqctl變成rabbitmqctl.bat, shell轉義是不同的:
# password is provided as a command line argument
rabbitmqctl.bat add_user 'username' '9a55f70a841f18b97c3a7db939b7adc9e34a0f1d'
列出用戶
rabbitmqctl list_users
rabbitmqctl.bat list_users
輸出可以更改為JSON:
rabbitmqctl list_users --formatter=json
rabbitmqctl.bat list_users --formatter=json
刪除用戶
rabbitmqctl delete_user 'username'
rabbitmqctl.bat delete_user 'username'
授予用戶權限
使用rabbitmqctl set_permissions
命令為虛擬主機中的用戶授予權限。
先添加vhost
rabbitmqctl add_vhost "custom-vhost"
# First ".*" for configure permission on every entity
# Second ".*" for write permission on every entity
# Third ".*" for read permission on every entity
rabbitmqctl set_permissions -p "custom-vhost" "username" ".*" ".*" ".*"
# First ".*" for configure permission on every entity
# Second ".*" for write permission on every entity
# Third ".*" for read permission on every entity
rabbitmqctl.bat set_permissions -p 'custom-vhost' 'username' '.*' '.*' '.*'
在虛擬主機中撤銷用戶權限
# Revokes permissions in a virtual host
rabbitmqctl clear_permissions -p "custom-vhost" "username"
# Revokes permissions in a virtual host
rabbitmqctl.bat clear_permissions -p 'custom-vhost' 'username'
多虛擬主機操作
每個rabbitmqctl
權限管理操作都是針對單個虛擬主機的。大容量操作必須腳本化,虛擬主機列表來自rabbitmqctl list_vhosts ——silent
:
# Assumes a Linux shell.
# Grants a user permissions to all virtual hosts.
for v in $(rabbitmqctl list_vhosts --silent); do rabbitmqctl set_permissions -p $v "a-user" ".*" ".*" ".*"; done
播種(預創建)用戶和權限
生產環境通常需要預先配置(種子)許多虛擬主機、用戶和用戶權限。
這可以通過以下幾種方式實現:
- 使用CLI Tools
- 在節點引導時導出和導入定義(推薦)
- 覆蓋配置文件中的默認憑據
CLI Tools
請參閱用戶管理一節。
節點啟動時導入
這個過程包括以下步驟:
- 使用CLI工具設置一個臨時節點,並創建必要的虛擬主機、用戶、權限等
- 將定義導出到定義文件
- 刪除文件中不相關的部分
- 配置節點在節點啟動時或之后導入文件
要了解更多信息,請參見定義指南中的節點啟動時導入定義。
節點啟動后導入定義
參見 importing definitions after node boot
覆蓋默認用戶憑據
可以使用兩個配置選項來覆蓋默認用戶憑據。這個用戶將只在第一次節點啟動時創建,因此它們必須在第一次啟動之前存在於配置文件中。
# default is "guest", and its access is limited to localhost only.
# See https://www.rabbitmq.com/access-control.html#default-state
default_user = a-user
# default is "guest"
default_pass = 768a852ed69ce916fa7faa278c962de3e4275e5f
和rabbitmq.conf
中的所有值一樣,#字符作為注釋的開頭,所以在生成的憑證中必須避免使用這個字符。
默認用戶憑據也可以加密。這需要使用高級配置文件advanced.config。配置值加密將詳細介紹此主題。
授權:權限如何工作
當RabbitMQ客戶端建立與服務器的連接並進行身份驗證時,它會指定一個虛擬主機來進行操作。此時將執行第一級訪問控制,服務器檢查用戶是否具有訪問虛擬主機的權限,否則將拒絕連接請求。
資源,如交換和隊列,是特定虛擬主機中的命名實體;相同的名稱在每個虛擬主機中表示不同的資源。當對資源執行某些操作時,執行第二級訪問控制。
RabbitMQ區分了對資源的配置操作、寫操作和讀操作。配置操作創建或銷毀資源,或改變其行為。寫操作將消息注入到資源中。而讀操作則從資源中檢索消息。
為了對資源執行操作,用戶必須被授予相應的權限。下表顯示了執行權限檢查的所有AMQP命令對什么類型的資源需要什么權限。
AMQP 0-9-1 Operation | configure | write | read | |
---|---|---|---|---|
exchange.declare | (passive=false) | exchange | ||
exchange.declare | (passive=true) | |||
exchange.declare | (with AE) | exchange | exchange (AE) | exchange |
exchange.delete | exchange | |||
queue.declare | (passive=false) | queue | ||
queue.declare | (passive=true) | |||
queue.declare | (with DLX) | queue | exchange (DLX) | queue |
queue.delete | queue | |||
exchange.bind | exchange (destination) | exchange (source) | ||
exchange.unbind | exchange (destination) | exchange (source) | ||
queue.bind | queue | exchange | ||
queue.unbind | queue | exchange | ||
basic.publish | exchange | |||
basic.get | queue | |||
basic.consume | queue | |||
queue.purge | queue |
權限在每個vhost的基礎上表示為三個正則表達式——分別用於配置、寫和讀。對於名稱匹配正則表達式的所有資源,用戶都被授予相應的操作權限。
為了方便,RabbitMQ在執行權限檢查時,將AMQP 0-9-1的默認exchange的空名映射為'amq.default'。
正則表達式'^$',即只匹配空字符串,覆蓋所有資源,有效地阻止用戶執行任何操作。內置的AMQP 0-9-1資源名以amq作為前綴。服務器生成的名稱以amq.gen作為前綴。
例如,'(amq.gen.*|amq.default)$'允許用戶訪問服務器生成的名稱和默認exchange。空字符串,"是'$'的同義詞,以完全相同的方式限制權限。
RabbitMQ可能會緩存每個連接或每個通道的訪問控制檢查結果。因此,對用戶權限的更改只有在用戶重新連接時才會生效。
關於如何設置訪問控制的詳細信息,請參見用戶管理部分以及rabbitmqctl的手冊頁面。
用戶標簽和管理用戶界面訪問
除了上面提到的權限之外,用戶還可以擁有與其關聯的標記。目前,只有管理UI訪問是由用戶標簽控制的。
標簽使用rabbitmqctl管理。默認情況下,新創建的用戶沒有設置任何標記。
請參考插件管理指南來了解更多關於什么標簽被支持,以及它們如何限制管理UI訪問。
Topic授權
自3.7.0版本起,RabbitMQ支持主題交換的主題授權。發布到topic exchange的消息的routing key會在強制發布授權時被考慮在內(例如,在RabbitMQ默認的授權后端,routing key會與一個正則表達式匹配,以決定消息是否可以被路由到下游)。主題授權以STOMP和MQTT等協議為目標,它們圍繞主題進行結構設計,並在底層使用topic exchanges。
主題授權是針對發布者的現有檢查之上附加的一層。將消息發布到主題類型的exchange 將經過basic.publish
和路由鍵檢查。如果前者拒絕訪問,則永遠不會調用后者。
也可以對主題消費者強制執行主題授權。請注意,對於不同的協議,它的工作方式是不同的。主題授權的概念只對面向主題的協議(如MQTT和STOMP)有意義。例如,在AMQP 0-9-1中,消費者從隊列中消費,因此應用標准資源權限。此外,對於AMQP 0-9-1,如果有,AMQP 0-9-1 topic exchange和隊列/交換之間的綁定路由鍵將根據配置的主題權限進行檢查。關於RabbitMQ如何處理主題授權的更多信息,請參閱STOMP和MQTT文檔指南。
當使用默認授權后端時,如果沒有定義主題權限,發布到topic exchange或消費主題總是被授權的(在一個新的RabbitMQ安裝中就是這樣)。有了這個授權后端,主題授權是可選的:你不需要白名單任何exchanges。因此,要使用主題授權,您需要為一個或多個exchanges選擇並定義主題權限。詳細信息請參見rabbitmqctl手冊頁面。
內部(默認)授權后端支持權限模式中的變量擴展。支持username、vhost、client_id三個變量。注意,client_id只適用於MQTT。例如tonyg是連接的用戶,則權限^{username}-.*
擴展為^tonyg-.*
。
如果使用了不同的授權后端(例如 LDAP、HTTP、AMQP),請參考這些后端文檔。
如果使用自定義的授權后端,則通過實現rabbit_authz_backend
行為的check_topic_access
回調來強制主題授權。
可替代的身份驗證和授權后端
身份驗證和授權是可插拔的。插件可以提供的實現
- authentication ("authn") backends
- authorisation ("authz") backends
插件可以同時提供這兩種功能。例如,internal、LDAP和HTTP后端就是這樣做的。
有些插件,例如 Source IP range one,只提供一個授權后端。身份驗證應該由內部數據庫、LDAP等來處理。
結合后端
可以使用auth_backends配置鍵為authn或authz使用多個后端。當使用多個身份驗證后端時,鏈中的后端返回的第一個正的結果被認為是最終結果。不應將其與混合后端混淆(例如,使用LDAP進行身份驗證,使用內部后端進行授權)。
下面的例子配置RabbitMQ只使用internal 后端(並且是默認的):
# rabbitmq.conf
#
# 1 here is a backend name. It can be anything.
# Since we only really care about backend
# ordering, we use numbers throughout this guide.
#
# "internal" is an alias for rabbit_auth_backend_internal
auth_backends.1 = internal
上面的例子使用了rabbit_auth_backend_internal的別名internal。以下別名可用:
internal
forrabbit_auth_backend_internal
ldap
forrabbit_auth_backend_ldap
(from the LDAP plugin)http
forrabbit_auth_backend_http
(from the HTTP auth backend plugin)amqp
forrabbit_auth_backend_amqp
(from the AMQP 0-9-1 auth backend plugin)dummy
forrabbit_auth_backend_dummy
當使用第三方插件時,需要提供完整的模塊名稱。
下面的例子配置RabbitMQ使用LDAP后端進行認證和授權。內部數據庫將不被參考:
auth_backends.1 = ldap
這將首先檢查LDAP,如果用戶不能通過LDAP認證,然后返回到內部數據庫:
auth_backends.1 = ldap
auth_backends.2 = internal
和上面一樣,但是會回到HTTP后端:
# rabbitmq.conf
#
auth_backends.1 = ldap
# uses module name instead of a short alias, "http"
auth_backends.2 = rabbit_auth_backend_http
# See HTTP backend docs for details
auth_http.user_path = http://my-authenticator-app/auth/user
auth_http.vhost_path = http://my-authenticator-app/auth/vhost
auth_http.resource_path = http://my-authenticator-app/auth/resource
auth_http.topic_path = http://my-authenticator-app/auth/topic
下面的例子配置RabbitMQ使用內部數據庫進行認證,使用source IP range backend進行授權:
# rabbitmq.conf
#
auth_backends.1.authn = internal
# uses module name because this backend is from a 3rd party
auth_backends.1.authz = rabbit_auth_backend_ip_range
下面的例子配置RabbitMQ使用LDAP后端進行認證,使用內部后端進行授權:
# rabbitmq.conf
#
auth_backends.1.authn = ldap
auth_backends.1.authz = internal
下面的例子相當高級。它將首先檢查LDAP。如果在LDAP中找到用戶,那么將根據LDAP檢查密碼,並根據內部數據庫執行后續的授權檢查(因此LDAP中的用戶也必須存在於內部數據庫中,但不需要在那里設置密碼)。如果用戶在LDAP中沒有找到,那么第二次嘗試僅使用內部數據庫:
# rabbitmq.conf
#
auth_backends.1.authn = ldap
auth_backends.1.authz = internal
auth_backends.2 = internal
用戶認證機制
RabbitMQ支持多種SASL認證機制。服務器中內置了三種這樣的機制:PLAIN、AMQPLAIN和RABBIT-CR-DEMO,還有一個——EXTERNAL——可以作為插件使用。
插件可以提供更多的身份驗證機制。有關通用插件開發的更多信息,請參閱插件開發指南。
內置的身份驗證機制
Mechanism | Description |
---|---|
PLAIN | SASL PLAIN 驗證。在RabbitMQ服務器和客戶端中,默認是啟用的,其他大多數客戶端也是默認的。 |
AMQPLAIN | PLAIN的非標准版本,用於向后兼容。該功能在RabbitMQ服務器中默認啟用。 |
EXTERNAL | 身份驗證使用帶外機制進行,例如x509證書對等驗證、客戶端IP地址范圍或類似的機制。這種機制通常由RabbitMQ插件提供。 |
RABBIT-CR-DEMO | 演示challenge-response認證的非標准機制。該機制具有與PLAIN等價的安全性,在RabbitMQ服務器中默認不啟用。 |
服務器中的配置
rabbit應用程序中的配置變量auth_mechanisms決定了為連接的客戶端提供哪些已安裝的機制。這個變量應該是一個與機制名稱對應的原子列表,例如默認情況下['PLAIN', 'AMQPLAIN']。服務器端列表被認為沒有任何特定的順序。請參見配置文件文檔。
客戶端機制配置
應用程序必須選擇使用不同的身份驗證機制,如EXTERNAL。
java中的配置
Java客戶端默認情況下不使用javax.security.sasl包,因為這在非oracle的jdk上是不可預測的,而在Android上完全沒有。有一個rabbitmq特有的SASL實現,通過SaslConfig接口配置。提供了一個DefaultSaslConfig類,使SASL配置在通常情況下更加方便。提供了一個類JDKSaslConfig來充當到javax.security.sasl的橋梁。
ConnectionFactory.getSaslConfig()和ConnectionFactory.setSaslConfig(SaslConfig)是與身份驗證機制交互的主要方法。
身份驗證故障排除
服務器日志將包含關於身份驗證失敗的條目:
2019-03-25 12:28:19.047 [info] <0.1613.0> accepting AMQP connection <0.1613.0> (127.0.0.1:63839 -> 127.0.0.1:5672)
2019-03-25 12:28:19.056 [error] <0.1613.0> Error on AMQP connection <0.1613.0> (127.0.0.1:63839 -> 127.0.0.1:5672, state: starting):
PLAIN login refused: user 'user2' - invalid credentials
2019-03-25 12:28:22.057 [info] <0.1613.0> closing AMQP connection <0.1613.0> (127.0.0.1:63839 -> 127.0.0.1:5672)
使用X.509證書進行身份驗證的連接上的身份驗證失敗將以不同的方式記錄。詳細信息請參見TLS故障處理指南。
rabbitmqctl authenticate_user
可以用來測試用戶名密碼對的認證:
rabbitmqctl authenticate_user "a-username" "a/password"
如果身份驗證成功,它將退出,代碼為0。如果出現故障,將使用非零的退出碼,並將打印失敗錯誤消息。
rabbitmqctl authenticate_user
將使用CLI-to-node通信連接,試圖通過內部API端點驗證用戶名/密碼對。假定連接是可信的。如果不是這樣,它的流量可以使用TLS加密。
根據AMQP 0-9-1規范,認證失敗應該導致服務器立即關閉TCP連接。然而,RabbitMQ客戶端可以選擇使用AMQP 0-9-1的認證失敗通知擴展來接收更具體的通知。現代客戶端庫對用戶透明地支持這種擴展:不需要配置,身份驗證失敗將導致特定編程語言或環境中使用的可見的返回錯誤、異常或其他溝通問題的方式。
授權故障排除
rabbitmqctl list_permissions
可以用來檢查用戶在給定虛擬主機中的權限:
rabbitmqctl list_permissions --vhost /
# => Listing permissions for vhost "/" ...
# => user configure write read
# => user2 .* .* .*
# => guest .* .* .*
# => temp-user .* .* .*
rabbitmqctl list_permissions --vhost gw1
# => Listing permissions for vhost "gw1" ...
# => user configure write read
# => guest .* .* .*
# => user2 ^user2 ^user2 ^user2
服務器日志將包含操作授權失敗的條目。例如,用戶對虛擬主機沒有任何權限:
2019-03-25 12:26:16.301 [info] <0.1594.0> accepting AMQP connection <0.1594.0> (127.0.0.1:63793 -> 127.0.0.1:5672)
2019-03-25 12:26:16.309 [error] <0.1594.0> Error on AMQP connection <0.1594.0> (127.0.0.1:63793 -> 127.0.0.1:5672, user: 'user2', state: opening):
access to vhost '/' refused for user 'user2'
2019-03-25 12:26:16.310 [info] <0.1594.0> closing AMQP connection <0.1594.0> (127.0.0.1:63793 -> 127.0.0.1:5672, vhost: 'none', user: 'user2')
授權失敗(權限違規)也會被記錄:
2019-03-25 12:30:05.209 [error] <0.1627.0> Channel error on connection <0.1618.0> (127.0.0.1:63881 -> 127.0.0.1:5672, vhost: 'gw1', user: 'user2'), channel 1:
operation queue.declare caused a channel exception access_refused: access to queue 'user3.q1' in vhost 'gw1' refused for user 'user2'
獲得幫助並提供反饋
如果你對本指南的內容或其他任何與RabbitMQ相關的話題有任何疑問,不要猶豫,可以在RabbitMQ郵件列表中詢問他們。