原文:https://www.rabbitmq.com/access-control.html

概述

不同的用户只能访问特定的虚拟主机。他们在每个虚拟主机中的权限也可以被限制。

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_userrabbitmqctl 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-.*

如果使用了不同的授权后端(例如LDAPHTTPAMQP),请参考这些后端文档。

如果使用自定义的授权后端,则通过实现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 for rabbit_auth_backend_internal
  • ldap for rabbit_auth_backend_ldap (from the LDAP plugin)
  • http for rabbit_auth_backend_http (from the HTTP auth backend plugin)
  • amqp for rabbit_auth_backend_amqp (from the AMQP 0-9-1 auth backend plugin)
  • dummy for rabbit_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邮件列表中询问他们。

最新文章

  1. 常见的http响应状态码
  2. TouchSlop与VelocityTracker认识
  3. iOS开发——高级篇——地图 MapKit
  4. Windows Server 2008 DNS服务器安装与配置
  5. (转帖)C++中自己实现的split函数
  6. missing required architecture x86_64 in file 不支持64位
  7. PHP性能分析
  8. Linux系统搭建负载均衡环境
  9. POJ3368(RMQ)
  10. Problem C: Andy&#39;s First Dictionary
  11. Xamarin.Android开发实践(四)
  12. chapter 12_2 保存无环的table
  13. setter getter 属性 点语法
  14. 5 个免费的受欢迎的 SQLite 管理工具【申明:来源于网络】
  15. org.springframework.transaction.CannotCreateTransactionException: Could not open Hibernate Session for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Cannot open con
  16. HDU3032 Nim or not Nim?
  17. Linux远程执行shell命令
  18. easyui combobox setValue方法不能触发onSelect事件
  19. javaweb购物车实现的几种方式
  20. pyDay10

热门文章

  1. [*]Quadratic Residual Networks: A New Class of Neural Networks for Solving Forward and Inverse Problems in Physics Involving PDEs
  2. FB50 过帐码 没有定义
  3. centos7.9重启后,主机名会变为IP地址
  4. 看K线学炒股(8.9)
  5. 三步建立自己域名的主页,Github Pages功能简明手册
  6. mathcurve.com
  7. 手机端 replaceAll没有执行吗
  8. jmeter的阶梯式加压性能测试 jp@gc - Stepping Thread Group (deprecated)
  9. java使用minio上传下载文件
  10. FFmpeg 命令行